from __future__ import annotations

import secrets

from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVectorField
from django.db import models
from simple_history.models import HistoricalRecords

from apps.core.authentication.models import Institution
from apps.core.storage.paths import (
    upload_to_alumni_certificates,
    upload_to_alumni_photos,
)

try:
    from pgvector.django import HnswIndex, VectorField
    _PGVECTOR_AVAILABLE = True
except ImportError:  # pragma: no cover
    _PGVECTOR_AVAILABLE = False
    VectorField = None  # type: ignore[assignment]
    HnswIndex = None  # type: ignore[assignment]


class AlumniSource(models.TextChoices):
    RIMS_SCHOLARSHIP = "rims_scholarship", "RIMS scholarship"
    REP_COMPLETION = "rep_completion", "REP course completion"
    MIXED = "mixed", "Mixed (RIMS + REP)"
    MANUAL = "manual", "Manual"
    CSV_IMPORT = "csv_import", "CSV import"


class AlumniProfile(models.Model):
    """Alumni overlay on top of ``CustomUser`` / ``UserProfile``.

    Bio / avatar / country live on ``UserProfile`` — do not duplicate here.
    ``visibility_consent`` is the single source of truth for directory
    visibility (GDPR opt-in); ``published_at`` is the audit timestamp.
    """

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="alumni_profile",
    )
    current_employer = models.CharField(max_length=255, blank=True)
    current_position = models.CharField(max_length=255, blank=True)
    current_institution = models.ForeignKey(
        Institution,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="alumni",
    )
    orcid_id = models.CharField(
        max_length=19,
        blank=True,
        db_index=True,
        help_text="ORCID iD in 0000-0000-0000-0000 form.",
    )
    linkedin_url = models.URLField(blank=True)
    twitter_url = models.URLField(blank=True)
    website_url = models.URLField(blank=True)
    researchgate_url = models.URLField(blank=True)
    photo = models.FileField(upload_to=upload_to_alumni_photos, blank=True, null=True)
    headline = models.CharField(
        max_length=200,
        blank=True,
        help_text="Short alumni tagline, distinct from the core UserProfile bio.",
    )
    expertise_tags = ArrayField(
        models.CharField(max_length=64),
        default=list,
        blank=True,
        help_text="Tags used by the mentorship matcher (e.g. 'agronomy', 'policy').",
    )
    mentor_accepting = models.BooleanField(
        default=False,
        help_text="Opt-in: accept incoming mentorship requests. Off hides you from the matcher.",
    )
    mentor_topics = ArrayField(
        models.CharField(max_length=80),
        default=list,
        blank=True,
        help_text="What you mentor on — shown to mentees on the discover page.",
    )
    mentor_capacity = models.PositiveSmallIntegerField(
        default=3,
        help_text="Max concurrent active mentees. The matcher stops recommending you once capacity is full.",
    )
    mentor_statement = models.TextField(
        blank=True,
        help_text="Short message to prospective mentees — your style, what you offer.",
    )
    visibility_consent = models.BooleanField(
        default=False,
        help_text="Opt-in for public alumni directory (GDPR).",
    )
    published_at = models.DateTimeField(null=True, blank=True)
    source = models.CharField(
        max_length=24,
        choices=AlumniSource.choices,
        default=AlumniSource.MANUAL,
    )
    source_refs = models.JSONField(
        default=dict,
        blank=True,
        help_text="Lineage e.g. {'graduation_ids': [12], 'rep_enrolment_ids': [340]}",
    )
    search_vector = SearchVectorField(null=True, editable=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    history = HistoricalRecords()

    class Meta:
        ordering = ["-published_at", "user__last_name"]
        indexes = [
            models.Index(fields=["visibility_consent", "published_at"]),
            GinIndex(fields=["search_vector"], name="alumni_profile_search_gin"),
        ]
        constraints = [
            models.UniqueConstraint(
                fields=["orcid_id"],
                condition=~models.Q(orcid_id=""),
                name="uniq_alumni_orcid_when_set",
            ),
        ]

    def __str__(self):
        return f"AlumniProfile<{self.user_id}>"

    @property
    def display_name(self) -> str:
        u = self.user
        full = f"{u.first_name} {u.last_name}".strip()
        return full or u.email

    @property
    def avatar_url(self) -> str:
        if self.photo:
            return self.photo.url
        # Fall back to core UserProfile avatar via CustomUser.avatar_url property.
        return getattr(self.user, "avatar_url", "") or ""


class DegreeType(models.TextChoices):
    PHD = "phd", "PhD"
    MSC = "msc", "MSc"
    MA = "ma", "MA"
    BSC = "bsc", "BSc"
    BA = "ba", "BA"
    DIPLOMA = "diploma", "Diploma"
    CERTIFICATE = "certificate", "Certificate"
    OTHER = "other", "Other"


class Degree(models.Model):
    profile = models.ForeignKey(
        AlumniProfile,
        on_delete=models.CASCADE,
        related_name="degrees",
    )
    institution = models.ForeignKey(
        Institution,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="alumni_degrees",
    )
    programme_name = models.CharField(max_length=255)
    degree_type = models.CharField(
        max_length=16,
        choices=DegreeType.choices,
        default=DegreeType.OTHER,
    )
    started_on = models.DateField(null=True, blank=True)
    graduated_on = models.DateField()
    thesis_title = models.CharField(max_length=500, blank=True)
    advisor_name = models.CharField(max_length=255, blank=True)
    funder = models.CharField(max_length=255, blank=True)

    class Meta:
        ordering = ["-graduated_on"]
        constraints = [
            models.UniqueConstraint(
                fields=["profile", "programme_name", "graduated_on"],
                name="uniq_degree_per_profile_programme_date",
            ),
        ]

    def __str__(self):
        return f"{self.get_degree_type_display()} — {self.programme_name}"


class EmploymentQuerySet(models.QuerySet):
    def current(self):
        return self.filter(ended_on__isnull=True)


class Employment(models.Model):
    profile = models.ForeignKey(
        AlumniProfile,
        on_delete=models.CASCADE,
        related_name="employments",
    )
    organisation = models.CharField(max_length=255)
    position = models.CharField(max_length=255)
    sector = models.CharField(max_length=128, blank=True)
    country = models.CharField(max_length=100, blank=True, help_text="Full country name, optional")
    started_on = models.DateField()
    ended_on = models.DateField(null=True, blank=True)
    description = models.TextField(blank=True)

    objects = EmploymentQuerySet.as_manager()

    class Meta:
        ordering = ["-started_on"]

    def __str__(self):
        return f"{self.position} @ {self.organisation}"


class PublicationType(models.TextChoices):
    JOURNAL = "journal", "Journal article"
    BOOK = "book", "Book"
    CHAPTER = "chapter", "Book chapter"
    CONFERENCE = "conference", "Conference paper"
    PREPRINT = "preprint", "Preprint"
    REPORT = "report", "Report"
    OTHER = "other", "Other"


class Publication(models.Model):
    profile = models.ForeignKey(
        AlumniProfile,
        on_delete=models.CASCADE,
        related_name="publications",
    )
    title = models.CharField(max_length=500)
    authors_text = models.TextField(blank=True)
    publication_type = models.CharField(
        max_length=16,
        choices=PublicationType.choices,
        default=PublicationType.JOURNAL,
    )
    doi = models.CharField(max_length=128, blank=True)
    url = models.URLField(blank=True)
    year = models.PositiveSmallIntegerField(null=True, blank=True)
    document = models.ForeignKey(
        "repository_documents.Document",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="alumni_publications",
    )
    cited_in_repository = models.BooleanField(default=False)

    class Meta:
        ordering = ["-year", "title"]
        constraints = [
            models.UniqueConstraint(
                fields=["profile", "doi"],
                condition=~models.Q(doi=""),
                name="uniq_publication_per_profile_doi",
            ),
        ]

    def __str__(self):
        return self.title[:80]


def _employer_feedback_token() -> str:
    return secrets.token_urlsafe(32)


class LinkedInSyncLog(models.Model):
    """Audit log for one-time LinkedIn OAuth pulls per profile.

    Mirrors :class:`apps.alumni.tracking.models.OrcidSyncLog` so the
    operations team can troubleshoot a sync the same way as for ORCID.
    """

    profile = models.OneToOneField(
        "alumni_profiles.AlumniProfile",
        on_delete=models.CASCADE,
        related_name="linkedin_sync_log",
    )
    linkedin_subject = models.CharField(max_length=64, blank=True, help_text="LinkedIn 'sub' identifier returned by /v2/userinfo.")
    last_attempt_at = models.DateTimeField(null=True, blank=True)
    last_success_at = models.DateTimeField(null=True, blank=True)
    last_error = models.TextField(blank=True)
    fields_filled = models.JSONField(default=list, blank=True, help_text="Profile fields the most recent sync wrote into.")

    def __str__(self):
        return f"LinkedInSyncLog<{self.profile_id}>"


class EmployerFeedbackInvitation(models.Model):
    """Tokenised invitation for an alumna/us's employer to complete a feedback survey.

    The associated :class:`apps.mel.feedback.TracerStudy` collects the actual
    responses so M&EL reporting picks them up automatically.
    """

    profile = models.ForeignKey(
        "alumni_profiles.AlumniProfile",
        on_delete=models.CASCADE,
        related_name="employer_feedback_invitations",
    )
    study = models.ForeignKey(
        "mel_feedback.TracerStudy",
        on_delete=models.CASCADE,
        related_name="alumni_employer_invitations",
    )
    employer_email = models.EmailField()
    employer_name = models.CharField(max_length=255, blank=True)
    token = models.CharField(
        max_length=64,
        unique=True,
        db_index=True,
        default=_employer_feedback_token,
    )
    invited_at = models.DateTimeField(auto_now_add=True)
    responded_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["-invited_at"]
        indexes = [
            models.Index(fields=["profile", "responded_at"]),
        ]

    def __str__(self):
        return f"EmployerFeedback<{self.employer_email}→{self.profile_id}>"


if _PGVECTOR_AVAILABLE:

    class AlumniProfileEmbedding(models.Model):
        profile = models.OneToOneField(
            "alumni_profiles.AlumniProfile",
            on_delete=models.CASCADE,
            related_name="embedding_row",
        )
        embedding = VectorField(dimensions=1536)
        source_text_hash = models.CharField(max_length=64, db_index=True, blank=True)
        last_synced_at = models.DateTimeField(auto_now=True)

        class Meta:
            indexes = [
                HnswIndex(
                    name="alumni_profile_embed_hnsw",
                    fields=["embedding"],
                    m=16,
                    ef_construction=64,
                    opclasses=["vector_cosine_ops"],
                ),
            ]

        def __str__(self):
            return f"AlumniProfileEmbedding<{self.profile_id}>"

else:  # pragma: no cover - degraded mode, no vector path

    class AlumniProfileEmbedding(models.Model):
        profile = models.OneToOneField(
            "alumni_profiles.AlumniProfile",
            on_delete=models.CASCADE,
            related_name="embedding_row",
        )
        # JSON fallback so semantic search degrades to no-op without crashing.
        embedding = models.JSONField(default=list, blank=True)
        source_text_hash = models.CharField(max_length=64, db_index=True, blank=True)
        last_synced_at = models.DateTimeField(auto_now=True)


class AlumniCertificate(models.Model):
    profile = models.ForeignKey(
        AlumniProfile,
        on_delete=models.CASCADE,
        related_name="certificates",
    )
    title = models.CharField(max_length=255)
    issuer = models.CharField(
        max_length=255,
        blank=True,
        help_text="Issuing body; use 'RUFORUM' for institute recognitions.",
    )
    issued_on = models.DateField(null=True, blank=True)
    file = models.FileField(
        upload_to=upload_to_alumni_certificates,
        blank=True,
        null=True,
    )
    verification_url = models.URLField(blank=True)
    rep_certificate_number = models.CharField(
        max_length=64,
        blank=True,
        db_index=True,
        help_text="Soft reference to rep.courses.Certificate.certificate_number.",
    )

    class Meta:
        ordering = ["-issued_on"]
        constraints = [
            models.UniqueConstraint(
                fields=["profile", "rep_certificate_number"],
                condition=~models.Q(rep_certificate_number=""),
                name="uniq_certificate_per_profile_rep_number",
            ),
        ]

    def __str__(self):
        return self.title
