import os
import uuid

from django.db import models
from django.conf import settings
from django.core.files.storage import default_storage


def jd_upload_path(instance, filename):
    """Store JD attachments as media/jd/<job_id><ext> (e.g. jd/101.pdf), overwriting."""
    ext = os.path.splitext(filename)[1].lower()
    if not instance.pk:
        return f"jd/{filename}"           # safety fallback (id not assigned yet)
    name = f"jd/{instance.pk}{ext}"
    if default_storage.exists(name):
        default_storage.delete(name)      # overwrite instead of appending a suffix
    return name


class JobDescription(models.Model):
    # Raw lifecycle status. Kept as the historical capitalized values that the
    # whole codebase (serializer validation, views, pipeline gating, frontend)
    # depends on. The single, user-facing canonical status is derived separately
    # and exposed as `jd_status` by the serializer (draft/pending_approval/
    # published/closed) — see JobDescriptionSerializer.get_jd_status.
    STATUS_CHOICES = [
        ("Draft", "Draft"),
        ("Published", "Published"),
        ("Closed", "Closed"),
    ]

    SHIFT_CHOICES = [
        ("Day", "Day"),
        ("Night", "Night"),
        ("Rotational", "Rotational"),
        ("Flexible", "Flexible"),
    ]

    PRIORITY_CHOICES = [
        ("High", "High"),
        ("Medium", "Medium"),
        ("Low", "Low"),
    ]

    # Non-sequential, unguessable public identifier for careers/LinkedIn URLs
    # (so public job links can't be enumerated by incrementing the DB id).
    public_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, null=True, db_index=True)
    title = models.CharField(max_length=255)
    department = models.CharField(max_length=150, blank=True, default="")
    location = models.CharField(max_length=255)
    experience_band = models.CharField(max_length=100, blank=True, default="", help_text="e.g. 3-5 years")
    ctc_band = models.CharField(max_length=100, blank=True, default="", help_text="e.g. 10-15 LPA")
    notice_period = models.CharField(max_length=100, blank=True, default="", help_text="e.g. 30 days / Immediate")
    shift = models.CharField(max_length=20, choices=SHIFT_CHOICES, blank=True, default="")
    must_have_skills = models.TextField(blank=True, default="")
    good_to_have_skills = models.TextField(blank=True, default="")
    qualifications = models.TextField(blank=True, default="")
    working_days = models.CharField(max_length=100, blank=True, default="", help_text="e.g. 5 days (Mon-Fri)")
    num_positions = models.PositiveIntegerField(null=True, blank=True, help_text="Number of open positions")
    certification = models.CharField(max_length=255, blank=True, default="", help_text="e.g. AWS Certified Developer")
    questions = models.JSONField(default=list, blank=True, help_text="Screening questions for this JD (list of strings).")
    rank_weights = models.JSONField(default=dict, blank=True,
        help_text="Per-JD AI ranking weights, e.g. {skills:40, experience:25, qualifications:15, role_fit:20}. Blank = global default.")
    attachment = models.FileField(upload_to=jd_upload_path, null=True, blank=True)
    work_details = models.TextField()
    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
        default="Draft"
    )
    priority = models.CharField(
        max_length=10,
        choices=PRIORITY_CHOICES,
        default="Medium",
    )
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="jobs"
    )
    client = models.ForeignKey(
        "clients.Client",
        on_delete=models.CASCADE,
        related_name="jobs",
        null=True,
        blank=True
    )
    # Recruiters assigned to work this JD (managed through JDRecruiterAssignment).
    assigned_recruiters = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        through="JDRecruiterAssignment",
        through_fields=("jd", "recruiter"),
        related_name="recruiter_jds",
        blank=True,
    )
    hunar_agent_id = models.CharField(
        max_length=100, blank=True, default="", help_text="Linked Hunar AI Agent ID."
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    # Workflow fields
    approval_status = models.CharField(max_length=30, default="DRAFT")
    submitted_for_approval_at = models.DateTimeField(null=True, blank=True)
    approved_at = models.DateTimeField(null=True, blank=True)
    rejected_at = models.DateTimeField(null=True, blank=True)
    approved_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="approved_jds"
    )
    rejection_reason = models.TextField(blank=True, default="")
    current_approver = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="approving_jds"
    )

    class Meta:
        db_table = "job_descriptions"
        ordering = ["-created_at"]
        # Custom JD-approval permissions — appear in the JOBS group of the
        # Edit Permissions popup, alongside the standard add/change/view perms.
        permissions = [
            ("submit_jd_for_approval", "Can submit JD for approval"),
            ("approve_reject_jd", "Can approve and reject JD"),
            ("add_jdcandidateassignment", "Can add jd candidate assignment"),
            ("view_jdcandidateassignment", "Can view jd candidate assignment"),
            ("delete_jdcandidateassignment", "Can delete jd candidate assignment"),
            ("change_jdcandidateassignment", "Can change jd candidate assignment"),
        ]

    def __str__(self):
        return f"{self.title} ({self.location})"

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if not getattr(self, "_skip_doc_sync", False):
            self._skip_doc_sync = True
            try:
                self.sync_attachment_title()
            finally:
                self._skip_doc_sync = False

    def sync_attachment_title(self):
        """Regenerate and update the attached .docx document with the latest form data."""
        try:
            import io
            import re
            from docx import Document
            from docx.shared import Inches, Pt, RGBColor
            from docx.enum.text import WD_ALIGN_PARAGRAPH
            from docx.enum.table import WD_TABLE_ALIGNMENT
            from django.core.files.base import ContentFile
            from django.core.files.storage import default_storage

            doc = Document()

            # Page Margins
            for section in doc.sections:
                section.top_margin = Inches(0.75)
                section.bottom_margin = Inches(0.75)
                section.left_margin = Inches(0.75)
                section.right_margin = Inches(0.75)

            # Document Header Title
            p_title = doc.add_paragraph()
            p_title.alignment = WD_ALIGN_PARAGRAPH.LEFT
            run_title = p_title.add_run(self.title or "Job Description")
            run_title.font.name = "Arial"
            run_title.font.size = Pt(20)
            run_title.font.bold = True
            run_title.font.color.rgb = RGBColor(64, 81, 137)

            # Metadata bar
            p_sub = doc.add_paragraph()
            run_sub = p_sub.add_run(f"Job Code: JD-{self.pk:04d}  |  TA-ATS Talent Acquisition Portal")
            run_sub.font.name = "Arial"
            run_sub.font.size = Pt(9)
            run_sub.font.color.rgb = RGBColor(100, 116, 139)

            doc.add_paragraph()

            # Section 1: Job Details
            p_h1 = doc.add_paragraph()
            run_h1 = p_h1.add_run("1. JOB DETAILS")
            run_h1.font.name = "Arial"
            run_h1.font.size = Pt(12)
            run_h1.font.bold = True
            run_h1.font.color.rgb = RGBColor(30, 41, 59)

            client_name = self.client.name if self.client else ""
            details_data = [
                ("Job Title", self.title),
                ("Associated Client", client_name),
                ("Department", self.department),
                ("Location", self.location),
                ("Experience Band", self.experience_band),
                ("CTC Band", self.ctc_band),
                ("Notice Period", self.notice_period),
                ("Shift", self.shift),
                ("Working Days", self.working_days),
                ("Number of Positions", str(self.num_positions) if self.num_positions is not None else ""),
                ("Priority", self.priority),
            ]

            table = doc.add_table(rows=0, cols=2)
            table.alignment = WD_TABLE_ALIGNMENT.CENTER
            table.autofit = False

            for label, val in details_data:
                if val is not None and str(val).strip():
                    row = table.add_row()
                    cell_0, cell_1 = row.cells[0], row.cells[1]
                    cell_0.width = Inches(2.2)
                    cell_1.width = Inches(4.5)

                    p0 = cell_0.paragraphs[0]
                    r0 = p0.add_run(f"{label}:")
                    r0.font.name = "Arial"
                    r0.font.size = Pt(10)
                    r0.font.bold = True
                    r0.font.color.rgb = RGBColor(71, 85, 105)

                    p1 = cell_1.paragraphs[0]
                    r1 = p1.add_run(str(val).strip())
                    r1.font.name = "Arial"
                    r1.font.size = Pt(10)
                    r1.font.color.rgb = RGBColor(30, 41, 59)

            doc.add_paragraph()

            # Requirements & Skills Section
            req_data = [
                ("Must-have Skills", self.must_have_skills),
                ("Good-to-have Skills", self.good_to_have_skills),
                ("Qualifications", self.qualifications),
                ("Certifications", self.certification),
            ]
            active_reqs = [(lbl, val.strip()) for lbl, val in req_data if val and val.strip()]
            if active_reqs:
                p_h2 = doc.add_paragraph()
                run_h2 = p_h2.add_run("REQUIREMENTS & SKILLS")
                run_h2.font.name = "Arial"
                run_h2.font.size = Pt(12)
                run_h2.font.bold = True
                run_h2.font.color.rgb = RGBColor(30, 41, 59)

                for label, val in active_reqs:
                    p_req = doc.add_paragraph()
                    r_lbl = p_req.add_run(f"{label}: ")
                    r_lbl.font.name = "Arial"
                    r_lbl.font.size = Pt(10)
                    r_lbl.font.bold = True
                    r_lbl.font.color.rgb = RGBColor(71, 85, 105)

                    r_val = p_req.add_run(val)
                    r_val.font.name = "Arial"
                    r_val.font.size = Pt(10)
                    r_val.font.color.rgb = RGBColor(30, 41, 59)

                doc.add_paragraph()

            # Section 2: Work Details & Description
            if self.work_details and self.work_details.strip():
                p_h3 = doc.add_paragraph()
                run_h3 = p_h3.add_run("2. WORK DETAILS & RESPONSIBILITIES")
                run_h3.font.name = "Arial"
                run_h3.font.size = Pt(12)
                run_h3.font.bold = True
                run_h3.font.color.rgb = RGBColor(30, 41, 59)

                clean_text = self.work_details.replace("<p>", "").replace("</p>", "\n")
                clean_text = clean_text.replace("<br>", "\n").replace("<br/>", "\n").replace("<br />", "\n")
                clean_text = clean_text.replace("<div>", "").replace("</div>", "\n")
                clean_text = re.sub(r"<.*?>", "", clean_text)
                clean_text = clean_text.replace("&nbsp;", " ").replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">")

                for line in clean_text.splitlines():
                    line_str = line.strip()
                    if line_str:
                        p_wd = doc.add_paragraph()
                        r_wd = p_wd.add_run(line_str)
                        r_wd.font.name = "Arial"
                        r_wd.font.size = Pt(10)
                        r_wd.font.color.rgb = RGBColor(30, 41, 59)

                doc.add_paragraph()

            # Section 3: Screening Questions & Answers
            questions_list = []
            if isinstance(self.questions, list):
                for q in self.questions:
                    if isinstance(q, str) and q.strip():
                        questions_list.append({"question": q.strip(), "answer": ""})
                    elif isinstance(q, dict) and q.get("question", "").strip():
                        questions_list.append({
                            "question": q.get("question", "").strip(),
                            "answer": q.get("answer", "").strip()
                        })

            if questions_list:
                p_h4 = doc.add_paragraph()
                run_h4 = p_h4.add_run("3. SCREENING QUESTIONS & ANSWERS")
                run_h4.font.name = "Arial"
                run_h4.font.size = Pt(12)
                run_h4.font.bold = True
                run_h4.font.color.rgb = RGBColor(30, 41, 59)

                for idx, q in enumerate(questions_list, 1):
                    p_q = doc.add_paragraph()
                    r_q_lbl = p_q.add_run(f"Q{idx}: ")
                    r_q_lbl.font.name = "Arial"
                    r_q_lbl.font.size = Pt(10)
                    r_q_lbl.font.bold = True
                    r_q_lbl.font.color.rgb = RGBColor(64, 81, 137)

                    r_q_txt = p_q.add_run(q["question"])
                    r_q_txt.font.name = "Arial"
                    r_q_txt.font.size = Pt(10)
                    r_q_txt.font.color.rgb = RGBColor(30, 41, 59)

                    if q.get("answer"):
                        p_a = doc.add_paragraph()
                        r_a_lbl = p_a.add_run(f"A{idx}: ")
                        r_a_lbl.font.name = "Arial"
                        r_a_lbl.font.size = Pt(10)
                        r_a_lbl.font.bold = True
                        r_a_lbl.font.color.rgb = RGBColor(16, 185, 129)

                        r_a_txt = p_a.add_run(q["answer"])
                        r_a_txt.font.name = "Arial"
                        r_a_txt.font.size = Pt(10)
                        r_a_txt.font.color.rgb = RGBColor(51, 65, 85)

            # Save in memory and write to storage
            out_io = io.BytesIO()
            doc.save(out_io)
            doc_bytes = out_io.getvalue()

            rel_name = f"jd/{self.pk}.docx"
            if default_storage.exists(rel_name):
                default_storage.delete(rel_name)
            default_storage.save(rel_name, ContentFile(doc_bytes))

            if self.attachment.name != rel_name:
                JobDescription.objects.filter(pk=self.pk).update(attachment=rel_name)
                self.attachment.name = rel_name

        except Exception as e:
            import logging
            logging.getLogger(__name__).warning("Error synchronizing attachment document: %s", e)


class JDApprovalHistory(models.Model):
    ACTION_CHOICES = [
        ("SUBMITTED", "Submitted"),
        ("APPROVED", "Approved"),
        ("REJECTED", "Rejected"),
        ("RESENT_FOR_APPROVAL", "Resent for Approval"),
    ]
    job_description = models.ForeignKey(
        JobDescription, on_delete=models.CASCADE, related_name="approval_histories"
    )
    action = models.CharField(max_length=30, choices=ACTION_CHOICES)
    action_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="jd_actions"
    )
    remarks = models.TextField(blank=True, default="")
    previous_status = models.CharField(max_length=50, blank=True, default="")
    new_status = models.CharField(max_length=50, blank=True, default="")
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "jd_approval_histories"
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.job_description.title} -> {self.action} by {self.action_by.email if self.action_by else 'System'}"


class JDApprovalRequest(models.Model):
    """One approval request per approver selected in the 'Send for Approval'
    popup. A JD can have several of these per submission (multi-approver)."""

    STATUS_CHOICES = [
        ("PENDING", "Pending"),
        ("APPROVED", "Approved"),
        ("REJECTED", "Rejected"),
        # Multi-approver synchronization: when a JD is sent to several Hiring
        # Managers, the first approval finalizes it. Every other still-pending
        # request is auto-closed to this terminal state so the Send History
        # shows "Already Approved" instead of a stale "Pending".
        ("AUTO_CLOSED", "Already Approved"),
    ]

    job_description = models.ForeignKey(
        JobDescription, on_delete=models.CASCADE, related_name="approval_requests"
    )
    approver = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="jd_approval_requests"
    )
    status = models.CharField(max_length=12, choices=STATUS_CHOICES, default="PENDING")
    sent_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True,
        related_name="sent_jd_approval_requests",
    )
    sent_at = models.DateTimeField(auto_now_add=True)
    acted_at = models.DateTimeField(null=True, blank=True)
    comments = models.TextField(blank=True, default="")

    class Meta:
        db_table = "jd_approval_requests"
        ordering = ["-sent_at"]

    def __str__(self):
        return f"JD {self.job_description_id} -> {self.approver.email} [{self.status}]"


class JDRecruiterAssignment(models.Model):
    """Join table linking a JobDescription to an assigned recruiter (User).

    One JD ↔ many recruiters, one recruiter ↔ many JDs. `assigned_by` records
    which manager/admin made the assignment.
    """

    jd = models.ForeignKey(
        JobDescription,
        on_delete=models.CASCADE,
        related_name="recruiter_assignments",
    )
    recruiter = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="jd_assignments",
    )
    assigned_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="jd_assignments_made",
    )
    assigned_at = models.DateTimeField(auto_now_add=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "jd_recruiter"
        ordering = ["-assigned_at"]
        # Prevent duplicate assignments of the same recruiter to the same JD.
        unique_together = ("jd", "recruiter")

    def __str__(self):
        return f"{self.recruiter_id} → JD {self.jd_id}"


class JobPosting(models.Model):
    """One row per (JD, channel) — tracks where a JD has been published.

    Providers live in services/integrations/; this model only records state.
    """

    class Channel(models.TextChoices):
        LINKEDIN = "LINKEDIN", "LinkedIn"
        NAUKRI = "NAUKRI", "Naukri"
        CAREER_PORTAL = "CAREER_PORTAL", "Career Portal"
        WHATSAPP = "WHATSAPP", "WhatsApp"
        SMS = "SMS", "SMS"
        TELEGRAM = "TELEGRAM", "Telegram"
        OTHER = "OTHER", "Other"

    class Status(models.TextChoices):
        PENDING = "PENDING", "Pending"
        POSTED = "POSTED", "Posted"
        FAILED = "FAILED", "Failed"

    job = models.ForeignKey(
        JobDescription,
        on_delete=models.CASCADE,
        related_name="postings",
    )
    channel = models.CharField(max_length=20, choices=Channel.choices)
    status = models.CharField(max_length=10, choices=Status.choices, default=Status.PENDING)
    external_post_id = models.CharField(max_length=255, blank=True, default="")
    external_url = models.URLField(max_length=500, blank=True, default="")
    error_message = models.TextField(blank=True, default="")
    posted_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="job_postings_made",
    )
    posted_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "job_postings"
        ordering = ["channel"]
        # One record per channel per JD — retries update the same row.
        unique_together = ("job", "channel")

    def __str__(self):
        return f"JD {self.job_id} → {self.channel} [{self.status}]"


class JobTrackingLink(models.Model):
    """Short, opaque tracking code behind a public application URL (`?t=<code>`).

    Replaces the ~180-character Fernet token in newly generated links with a
    10-character random code. Security is equivalent but achieved differently:
    the code carries no payload at all — it is a pure server-side reference, so
    there is nothing to decode and nothing to tamper with. The JD, the platform
    channel and the tracking metadata live only in this row, and only the
    backend can map a code back to them.

    Fernet tokens are NOT removed: links already published (stored on
    JobPosting.external_url, shared on LinkedIn, sent over WhatsApp) keep
    resolving through the legacy path in tracking_tokens.py.

    One stable code per (job, channel, label) — previewing a link and then
    publishing it yields the same URL, and re-previewing never churns codes.
    `label` exists so a future campaign-specific link for the same platform can
    get its own code without changing this model.
    """

    # Unambiguous alphabet — no 0/O or 1/I/l, so a code stays readable if it is
    # ever transcribed by hand from an SMS.
    ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    CODE_LENGTH = 10

    code = models.CharField(max_length=32, unique=True, db_index=True)
    job = models.ForeignKey(
        JobDescription, on_delete=models.CASCADE, related_name="tracking_links"
    )
    channel = models.CharField(max_length=20, choices=JobPosting.Channel.choices)
    label = models.CharField(
        max_length=100, blank=True, default="",
        help_text="Optional campaign label — lets one platform have several distinct links.",
    )
    tracking = models.JSONField(
        default=dict, blank=True,
        help_text="Free-form tracking metadata resolved alongside the JD and channel.",
    )
    # Attribution counters — updated when a token is resolved for an application.
    use_count = models.PositiveIntegerField(default=0)
    last_used_at = models.DateTimeField(null=True, blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True,
        related_name="job_tracking_links_made",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "job_tracking_links"
        ordering = ["job", "channel"]
        unique_together = ("job", "channel", "label")

    def __str__(self):
        return f"{self.code} → JD {self.job_id} [{self.channel}]"

    @classmethod
    def generate_code(cls) -> str:
        """A fresh unguessable code. The unique constraint is the real guard;
        this only avoids the obvious collision before hitting the DB."""
        import secrets
        for _ in range(10):
            code = "".join(secrets.choice(cls.ALPHABET) for _ in range(cls.CODE_LENGTH))
            if not cls.objects.filter(code=code).exists():
                return code
        # Astronomically unlikely — widen rather than fail.
        return "".join(secrets.choice(cls.ALPHABET) for _ in range(cls.CODE_LENGTH + 4))


class JDAuditLog(models.Model):
    """Change history for a Job Description — one row per create/update/delete/status change."""
    class Action(models.TextChoices):
        CREATED = "CREATED", "Created"
        UPDATED = "UPDATED", "Updated"
        DELETED = "DELETED", "Deleted"
        STATUS_CHANGED = "STATUS_CHANGED", "Status Changed"
        RECRUITERS_ASSIGNED = "RECRUITERS_ASSIGNED", "Recruiters Assigned"
        POSTED = "POSTED", "Posted to Channel"

    job = models.ForeignKey(
        JobDescription, on_delete=models.SET_NULL, null=True, blank=True, related_name="audit_logs"
    )
    job_title = models.CharField(max_length=255, blank=True, default="")  # kept even if JD deleted
    action = models.CharField(max_length=30, choices=Action.choices)
    changes = models.TextField(blank=True, default="", help_text="Human-readable summary of what changed.")
    performed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="jd_audit_actions"
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "jd_audit_logs"
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.action} on JD {self.job_id} by {self.performed_by_id}"


from django.db.models.signals import post_delete, pre_save
from django.dispatch import receiver


@receiver(post_delete, sender=JobDescription)
def _delete_attachment_on_job_delete(sender, instance, **kwargs):
    """Remove the attachment file from disk when its job is deleted."""
    if instance.attachment:
        instance.attachment.delete(save=False)


@receiver(pre_save, sender=JobDescription)
def _delete_old_attachment_on_replace(sender, instance, **kwargs):
    """When a job's attachment is replaced, delete the old file."""
    if not instance.pk:
        return
    old = JobDescription.objects.filter(pk=instance.pk).first()
    if old and old.attachment and old.attachment != instance.attachment:
        old.attachment.delete(save=False)


class LinkedInAuth(models.Model):
    """Stores the LinkedIn OAuth access token obtained via the 3-legged flow (JD-006).
    Singleton-ish: we always use the most recent row."""
    access_token = models.TextField(blank=True, default="")
    author_urn = models.CharField(max_length=120, blank=True, default="", help_text="urn:li:organization:<id>")
    scope = models.CharField(max_length=255, blank=True, default="")
    expires_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "linkedin_auth"
        ordering = ["-created_at"]

    @classmethod
    def current(cls):
        return cls.objects.first()
