"""SME-Hub onboarding models — Subprocess SP1.

Implements PRD Tables 35–36 (FRSME-OBR001 → FRSME-OBR037):
  • RegistrationRecord (FRSME-OBR002, OBR009)
  • EntrepreneurProfile / Mentor / Investor / ServiceProvider / Partner / Buyer profiles
  • Business + BusinessBaseline (FRSME-OBR021–025, OBR035)
  • AIHAffiliation with RIMS grant cross-reference (FRSME-OBR030–034)

FSM transitions live on the models (matches the project convention used in
`apps/rims/grants/models.py`).  Domain side-effects (notifications, audit log
writes, M&E baseline emission) are dispatched from `services.py`.
"""
from __future__ import annotations

import secrets
import uuid

from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils import timezone
from django_fsm import FSMField, transition

from apps.core.authentication.models import Institution
from apps.core.storage.paths import upload_to_smehub_onboarding


# ---------------------------------------------------------------------------
# Registration record  (FRSME-OBR001 / OBR002 / OBR009)
# ---------------------------------------------------------------------------

class RegistrationRecord(models.Model):
    """Persists the registration form *before* email verification.

    PRD §3.2.6 frustrating bug: the legacy SME-Hub lost form data when email
    verification was delayed.  FRSME-OBR002 mandates immediate persistence;
    FRSME-OBR009 lets the user resend a verification link without re-entering
    any details.  The token is single-use and time-limited.
    """

    class Role(models.TextChoices):
        ENTREPRENEUR = "entrepreneur", "Entrepreneur"
        MENTOR = "mentor", "Mentor"
        INVESTOR = "investor", "Investor"
        SERVICE_PROVIDER = "service_provider", "Service provider"
        PARTNER = "partner", "Partner organisation"
        BUYER = "buyer", "Buyer / off-taker"

    full_name = models.CharField(max_length=255)
    email = models.EmailField(db_index=True)
    country = models.CharField(max_length=100, blank=True, help_text="Full country name, optional")
    organisation = models.CharField(max_length=255, blank=True)
    phone = models.CharField(max_length=32, blank=True)
    role = models.CharField(max_length=32, choices=Role.choices)
    password_hash = models.CharField(max_length=128, help_text="Pre-hashed via make_password.")
    token = models.CharField(max_length=64, unique=True, db_index=True)
    token_expires_at = models.DateTimeField()
    consumed_at = models.DateTimeField(null=True, blank=True)
    created_user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_registration",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]
        indexes = [models.Index(fields=["email", "consumed_at"])]

    def __str__(self) -> str:
        return f"{self.email} ({self.role})"

    @classmethod
    def fresh_token(cls) -> str:
        return secrets.token_urlsafe(32)

    @property
    def is_active(self) -> bool:
        return self.consumed_at is None and timezone.now() < self.token_expires_at


# ---------------------------------------------------------------------------
# Entrepreneur profile  (FRSME-OBR011 → OBR020)
# ---------------------------------------------------------------------------

class EntrepreneurProfile(models.Model):
    class Status(models.TextChoices):
        PENDING_VERIFICATION = "pending_verification", "Pending verification"
        PENDING_MORE_INFO = "pending_more_info", "Pending more info"
        VERIFIED = "verified", "Verified"
        REJECTED = "rejected", "Rejected"

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="smehub_entrepreneur",
    )
    country = models.CharField(max_length=100, blank=True)
    organisation = models.CharField(max_length=255, blank=True)
    phone = models.CharField(max_length=32, blank=True)
    bio = models.TextField(blank=True, help_text="Short professional summary.")
    photo = models.FileField(upload_to=upload_to_smehub_onboarding, null=True, blank=True)
    verification_status = FSMField(
        max_length=32,
        choices=Status.choices,
        default=Status.PENDING_VERIFICATION,
        protected=True,
        db_index=True,
    )
    rejection_reason = models.TextField(blank=True)
    info_request_message = models.TextField(blank=True)
    # FRSME-OBR011/OBR012 — RIMS beneficiary cross-reference
    rims_match_application = models.ForeignKey(
        "rims_grants.Application",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_entrepreneur_matches",
    )
    rims_match_scholar = models.ForeignKey(
        "rims_scholarships.Scholar",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_entrepreneur_matches",
    )
    rims_match_confirmed = models.BooleanField(default=False)
    verified_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_entrepreneurs_verified",
    )
    verified_at = models.DateTimeField(null=True, blank=True)
    submitted_at = models.DateTimeField(null=True, blank=True)
    # Founder context (M&E youth signal). Personal demographics — gender, date
    # of birth — live on core.UserProfile (already feeds the AR010 women & youth
    # indicator); these two are SME-Hub-specific founder attributes from the
    # imported `businesses` table (is_student true for 348/474 imported founders).
    is_student = models.BooleanField(default=False)
    education_institution = models.CharField(max_length=255, blank=True)
    external_ref = models.PositiveBigIntegerField(
        null=True,
        blank=True,
        unique=True,
        db_index=True,
        help_text="Source id from the SME-Hub `businesses` row (founder side).",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]
        indexes = [models.Index(fields=["verification_status", "country"])]

    def __str__(self) -> str:
        return f"Entrepreneur {self.user.email}"

    @transition(
        field=verification_status,
        source=[Status.PENDING_VERIFICATION, Status.PENDING_MORE_INFO, Status.REJECTED],
        target=Status.VERIFIED,
    )
    def verify(self, *, by_user) -> None:
        self.verified_by = by_user
        self.verified_at = timezone.now()
        self.rejection_reason = ""
        self.info_request_message = ""

    @transition(
        field=verification_status,
        source=[Status.PENDING_VERIFICATION, Status.PENDING_MORE_INFO],
        target=Status.REJECTED,
    )
    def reject(self, *, reason: str) -> None:
        self.rejection_reason = reason

    @transition(
        field=verification_status,
        source=[Status.PENDING_VERIFICATION, Status.PENDING_MORE_INFO],
        target=Status.PENDING_MORE_INFO,
    )
    def request_more_info(self, *, message: str) -> None:
        self.info_request_message = message

    @transition(
        field=verification_status,
        source=Status.REJECTED,
        target=Status.PENDING_VERIFICATION,
    )
    def resubmit(self) -> None:
        self.rejection_reason = ""
        self.submitted_at = timezone.now()


# ---------------------------------------------------------------------------
# Other role profiles (lightweight; richer detail lives in their own modules)
# ---------------------------------------------------------------------------

class _RoleProfileBase(models.Model):
    class Status(models.TextChoices):
        PENDING_VERIFICATION = "pending_verification", "Pending verification"
        VERIFIED = "verified", "Verified"
        REJECTED = "rejected", "Rejected"

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="+",
    )
    country = models.CharField(max_length=100, blank=True)
    organisation = models.CharField(max_length=255, blank=True)
    phone = models.CharField(max_length=32, blank=True)
    bio = models.TextField(blank=True)
    verification_status = FSMField(
        max_length=32,
        choices=Status.choices,
        default=Status.PENDING_VERIFICATION,
        protected=True,
        db_index=True,
    )
    rejection_reason = models.TextField(blank=True)
    verified_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    verified_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

    @transition(
        field=verification_status,
        source=[Status.PENDING_VERIFICATION, Status.REJECTED],
        target=Status.VERIFIED,
    )
    def verify(self, *, by_user) -> None:
        self.verified_by = by_user
        self.verified_at = timezone.now()
        self.rejection_reason = ""

    @transition(
        field=verification_status,
        source=Status.PENDING_VERIFICATION,
        target=Status.REJECTED,
    )
    def reject(self, *, reason: str) -> None:
        self.rejection_reason = reason


class MentorshipCategory(models.Model):
    """Controlled mentorship area (imported `mentorship_categories`)."""

    name = models.CharField(max_length=255, unique=True)
    external_ref = models.PositiveBigIntegerField(
        null=True, blank=True, unique=True, db_index=True
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["name"]
        verbose_name_plural = "mentorship categories"

    def __str__(self) -> str:
        return self.name


class MentorProfile(_RoleProfileBase):
    expertise_sectors = models.JSONField(
        default=list,
        blank=True,
        help_text="List of sector tags this mentor specialises in.",
    )
    years_experience = models.PositiveSmallIntegerField(null=True, blank=True)
    languages = models.JSONField(default=list, blank=True)
    # Enriched professional record (imported `mentors` table). Personal
    # demographics (gender, date of birth) live on core.UserProfile; base
    # already carries country / organisation / phone / bio.
    current_company = models.CharField(max_length=255, blank=True)
    current_position = models.CharField(max_length=255, blank=True)
    linkedin = models.URLField(max_length=255, blank=True)
    nationality = models.CharField(max_length=100, blank=True)
    awards = models.TextField(blank=True)
    publications = models.TextField(blank=True)
    resume = models.FileField(upload_to=upload_to_smehub_onboarding, null=True, blank=True)
    categories = models.ManyToManyField(
        MentorshipCategory,
        blank=True,
        related_name="mentors",
    )
    external_ref = models.PositiveBigIntegerField(
        null=True, blank=True, unique=True, db_index=True,
        help_text="Source id from the SME-Hub `mentors` table.",
    )

    class Meta(_RoleProfileBase.Meta):
        abstract = False
        ordering = ["-created_at"]

    def __str__(self) -> str:
        return f"Mentor {self.user.email}"


class InvestorProfile(_RoleProfileBase):
    """Light onboarding profile.  Full investor record (with ticket size,
    geographic preference, focus sectors) lives in
    `apps.smehub.investment.Investor` and is created by SP5 services."""

    organisation_type = models.CharField(
        max_length=32,
        blank=True,
        help_text="Angel / VC / DFI / donor / foundation / government programme.",
    )

    class Meta(_RoleProfileBase.Meta):
        abstract = False
        ordering = ["-created_at"]

    def __str__(self) -> str:
        return f"Investor {self.user.email}"


class ServiceProviderProfile(_RoleProfileBase):
    services_offered = models.JSONField(default=list, blank=True)

    class Meta(_RoleProfileBase.Meta):
        abstract = False
        ordering = ["-created_at"]

    def __str__(self) -> str:
        return f"Service provider {self.user.email}"


class PartnerProfile(_RoleProfileBase):
    organisation_type = models.CharField(
        max_length=32,
        blank=True,
        help_text="government / corporate / NGO / university.",
    )

    class Meta(_RoleProfileBase.Meta):
        abstract = False
        ordering = ["-created_at"]

    def __str__(self) -> str:
        return f"Partner {self.user.email}"


class BuyerProfile(_RoleProfileBase):
    sectors_sourced = models.JSONField(default=list, blank=True)

    class Meta(_RoleProfileBase.Meta):
        abstract = False
        ordering = ["-created_at"]

    def __str__(self) -> str:
        return f"Buyer {self.user.email}"


# ---------------------------------------------------------------------------
# Business + Baseline + Affiliation  (FRSME-OBR021 → OBR036)
# ---------------------------------------------------------------------------

class BusinessStage(models.TextChoices):
    IDEA = "idea", "Idea / pre-seed"
    MVP = "mvp", "MVP / prototype"
    EARLY_REVENUE = "early_revenue", "Early revenue"
    GROWTH = "growth", "Growth"
    SCALE = "scale", "Scale"


class RevenueRange(models.TextChoices):
    NONE = "none", "No revenue yet"
    UNDER_1K = "under_1k", "Under $1k / month"
    R_1K_10K = "1k_10k", "$1k–10k / month"
    R_10K_50K = "10k_50k", "$10k–50k / month"
    R_50K_250K = "50k_250k", "$50k–250k / month"
    OVER_250K = "over_250k", "Over $250k / month"


class Business(models.Model):
    class Status(models.TextChoices):
        PENDING_ADMIN_REVIEW = "pending_admin_review", "Pending admin review"
        VERIFIED = "verified", "Verified"
        REJECTED = "rejected", "Rejected"
        REVOKED = "revoked", "Revoked"

    entrepreneur = models.ForeignKey(
        EntrepreneurProfile,
        on_delete=models.CASCADE,
        related_name="businesses",
    )
    business_id = models.CharField(
        max_length=20,
        unique=True,
        editable=False,
        help_text="Auto-generated public identifier (FRSME-OBR024).",
    )
    name = models.CharField(max_length=255)
    sector = models.CharField(max_length=128, db_index=True)
    sub_sector = models.CharField(max_length=128, blank=True)
    business_stage = models.CharField(
        max_length=32,
        choices=BusinessStage.choices,
        db_index=True,
    )
    founding_date = models.DateField(null=True, blank=True)
    country = models.CharField(max_length=100, blank=True, db_index=True)
    location = models.CharField(max_length=255, blank=True)
    description = models.TextField(blank=True)
    target_market = models.TextField(blank=True)
    pitch = models.TextField(blank=True, help_text="Short elevator pitch (imported `businesses.pitch`).")
    logo = models.FileField(upload_to=upload_to_smehub_onboarding, null=True, blank=True)
    # Business identity & contact (imported `businesses` table).
    registration_number = models.CharField(max_length=120, blank=True)
    business_email = models.EmailField(blank=True)
    website = models.URLField(max_length=255, blank=True)
    phone = models.CharField(max_length=40, blank=True)
    # Employment (gender-disaggregated M&E — female_employees is the only
    # reliable gender datum in the imported data; founder `gender` was defaulted).
    total_employees = models.PositiveIntegerField(null=True, blank=True)
    female_employees = models.PositiveIntegerField(null=True, blank=True)
    # UltimatePOS bridge: links this SME to its bookkeeping tenant
    # (imported `businesses.accounting_business_id`; 124/474 populated). BigInteger
    # because the source column is int(10) unsigned (values exceed 2^31).
    accounting_business_id = models.PositiveBigIntegerField(
        null=True, blank=True, unique=True, db_index=True
    )
    stage_raw = models.CharField(
        max_length=64, blank=True,
        help_text="Original stage label {Start,Idea,Break,Profitable} before mapping.",
    )
    external_ref = models.PositiveBigIntegerField(
        null=True, blank=True, unique=True, db_index=True,
        help_text="Source id from the SME-Hub `businesses` table.",
    )
    verification_status = FSMField(
        max_length=32,
        choices=Status.choices,
        default=Status.PENDING_ADMIN_REVIEW,
        protected=True,
        db_index=True,
    )
    rejection_reason = models.TextField(blank=True)
    revoke_reason = models.TextField(blank=True)
    programme_of_origin = models.ForeignKey(
        "smehub_incubation.Programme",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="origin_businesses",
        help_text=(
            "First incubation cohort that admitted this business. Set on "
            "cohort confirmation and never overwritten — used by SP6 "
            "cross-system reports to attribute outcomes to source programme."
        ),
    )
    verified_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_businesses_verified",
    )
    verified_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["sector", "business_stage", "verification_status"]),
            models.Index(fields=["country", "verification_status"]),
        ]

    def __str__(self) -> str:
        return f"{self.business_id} — {self.name}"

    def save(self, *args, **kwargs):
        if not self.business_id:
            self.business_id = self._generate_business_id()
        super().save(*args, **kwargs)

    @staticmethod
    def _generate_business_id() -> str:
        return f"SME-{uuid.uuid4().hex[:8].upper()}"

    @transition(
        field=verification_status,
        source=[Status.PENDING_ADMIN_REVIEW, Status.REJECTED],
        target=Status.VERIFIED,
    )
    def verify(self, *, by_user) -> None:
        self.verified_by = by_user
        self.verified_at = timezone.now()
        self.rejection_reason = ""
        self.revoke_reason = ""

    @transition(
        field=verification_status,
        source=Status.PENDING_ADMIN_REVIEW,
        target=Status.REJECTED,
    )
    def reject(self, *, reason: str) -> None:
        self.rejection_reason = reason

    @transition(
        field=verification_status,
        source=Status.VERIFIED,
        target=Status.REVOKED,
    )
    def revoke(self, *, reason: str) -> None:
        self.revoke_reason = reason


class BusinessBaseline(models.Model):
    """M&E baseline snapshot recorded at SP1 completion.

    PRD FRSME-OBR023 (capture during business reg) and FRSME-OBR035 (lock-in
    on AIH affiliation completion).  Read by M&EL to seed the longitudinal
    tracking record (FRSME-MEI001).
    """

    business = models.OneToOneField(
        Business,
        on_delete=models.CASCADE,
        related_name="baseline",
    )
    entered_at = models.DateTimeField(default=timezone.now)
    business_stage_at_entry = models.CharField(max_length=32, choices=BusinessStage.choices)
    fte_count = models.PositiveIntegerField(default=0)
    pte_count = models.PositiveIntegerField(default=0)
    revenue_range = models.CharField(
        max_length=32,
        choices=RevenueRange.choices,
        default=RevenueRange.NONE,
    )
    primary_target_market = models.CharField(max_length=255, blank=True)
    geographic_reach = models.CharField(
        max_length=255,
        blank=True,
        help_text="Comma-separated countries or regional descriptor.",
    )
    locked_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When the baseline was committed to M&E (FRSME-OBR035).",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self) -> str:
        return f"Baseline for {self.business.business_id}"


class AIHAffiliation(models.Model):
    """Multi-affiliation entrepreneur ↔ institution link with RIMS programme cross-reference.

    Implements FRSME-OBR030 → OBR034.  An entrepreneur may declare ties to
    several AIH / partner universities; exactly one is flagged primary.
    When the programme of origin is RIMS-funded, the system resolves the
    linkage to a concrete `Award` and stores it for analytics.
    """

    class AffiliationNature(models.TextChoices):
        STUDENT = "student", "Student"
        ALUMNI = "alumni", "Alumni"
        FACULTY = "faculty", "Faculty"
        EXTERNAL = "external", "External community entrepreneur"

    business = models.ForeignKey(
        Business,
        on_delete=models.CASCADE,
        related_name="affiliations",
    )
    institution = models.ForeignKey(
        Institution,
        on_delete=models.PROTECT,
        related_name="smehub_affiliations",
    )
    nature = models.CharField(max_length=16, choices=AffiliationNature.choices)
    programme_of_origin = models.CharField(max_length=255, blank=True)
    is_primary = models.BooleanField(default=False, db_index=True)
    rims_award = models.ForeignKey(
        "rims_grants.Award",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_affiliations",
        help_text="RIMS award resolved when programme_of_origin is grant-funded.",
    )
    rims_scholarship_record = models.ForeignKey(
        "rims_scholarships.Scholar",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_affiliations",
    )
    set_by_admin = models.BooleanField(default=False, help_text="FRSME-OBR034 backend override flag.")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-is_primary", "-created_at"]
        constraints = [
            models.UniqueConstraint(
                fields=["business", "institution"],
                name="uniq_smehub_business_institution",
            ),
            # Only one primary affiliation per business
            models.UniqueConstraint(
                fields=["business"],
                condition=models.Q(is_primary=True),
                name="uniq_smehub_primary_affiliation",
            ),
        ]

    def __str__(self) -> str:
        flag = " (primary)" if self.is_primary else ""
        return f"{self.business.business_id} — {self.institution.name}{flag}"


# ---------------------------------------------------------------------------
# Innovation onboarding & vetting  (Phase 6.1 — build_order.md)
# ---------------------------------------------------------------------------


class Innovation(models.Model):
    """An innovation proposed by a verified Business and put through vetting.

    Lifecycle (FSM): Draft → Submitted → UnderVetting → (Approved | Rejected
    | RevisionsRequested → Submitted) → optional onboard() → public catalogue.
    """

    class Status(models.TextChoices):
        DRAFT = "draft", "Draft"
        SUBMITTED = "submitted", "Submitted"
        UNDER_VETTING = "under_vetting", "Under vetting"
        REVISIONS_REQUESTED = "revisions_requested", "Revisions requested"
        APPROVED = "approved", "Approved"
        REJECTED = "rejected", "Rejected"
        WITHDRAWN = "withdrawn", "Withdrawn"

    class Stage(models.TextChoices):
        IDEA = "idea", "Idea"
        PROTOTYPE = "prototype", "Prototype"
        PILOT = "pilot", "Pilot"
        SCALING = "scaling", "Scaling"
        MATURE = "mature", "Mature"

    business = models.ForeignKey(
        Business,
        on_delete=models.CASCADE,
        related_name="innovations",
    )
    innovation_id = models.CharField(
        max_length=20,
        unique=True,
        editable=False,
        help_text="Auto-generated public identifier (INN-XXXXXXXX).",
    )
    title = models.CharField(max_length=255)
    tagline = models.CharField(max_length=255, blank=True)
    description = models.TextField(blank=True)
    problem_statement = models.TextField(blank=True)
    solution_summary = models.TextField(blank=True)
    market_focus = models.TextField(blank=True)
    target_segments = models.JSONField(default=list, blank=True)
    sector = models.CharField(max_length=128, db_index=True)
    sub_sector = models.CharField(max_length=128, blank=True)
    country = models.CharField(max_length=100, blank=True, db_index=True)
    stage = models.CharField(max_length=24, choices=Stage.choices, default=Stage.IDEA)
    team_size = models.PositiveSmallIntegerField(default=1)
    team_summary = models.TextField(blank=True)
    cover_image = models.FileField(
        upload_to=upload_to_smehub_onboarding,
        null=True,
        blank=True,
    )
    pitch_deck = models.FileField(
        upload_to=upload_to_smehub_onboarding,
        null=True,
        blank=True,
    )
    business_model_canvas = models.FileField(
        upload_to=upload_to_smehub_onboarding,
        null=True,
        blank=True,
    )
    lifecycle_status = FSMField(
        max_length=32,
        choices=Status.choices,
        default=Status.DRAFT,
        protected=True,
        db_index=True,
    )
    is_public = models.BooleanField(
        default=False,
        help_text="Flips True on onboard(); controls visibility in the public catalogue.",
    )
    submitted_at = models.DateTimeField(null=True, blank=True)
    vetting_started_at = models.DateTimeField(null=True, blank=True)
    decided_at = models.DateTimeField(null=True, blank=True)
    published_at = models.DateTimeField(null=True, blank=True)
    feedback_to_innovator = models.TextField(blank=True)
    rejection_reason = models.TextField(blank=True)
    withdrawn_reason = models.TextField(blank=True)
    decided_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_innovations_decided",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["sector", "lifecycle_status"]),
            models.Index(fields=["country", "lifecycle_status"]),
            models.Index(fields=["lifecycle_status", "is_public"]),
        ]

    def __str__(self) -> str:
        return f"{self.innovation_id} — {self.title}"

    def save(self, *args, **kwargs):
        if not self.innovation_id:
            self.innovation_id = f"INN-{uuid.uuid4().hex[:8].upper()}"
        super().save(*args, **kwargs)

    @transition(
        field=lifecycle_status,
        source=[Status.DRAFT, Status.REVISIONS_REQUESTED],
        target=Status.SUBMITTED,
    )
    def submit(self, *, by_user) -> None:
        self.submitted_at = timezone.now()
        self.feedback_to_innovator = ""

    @transition(
        field=lifecycle_status,
        source=Status.SUBMITTED,
        target=Status.UNDER_VETTING,
    )
    def start_vetting(self, *, by_user) -> None:
        self.vetting_started_at = timezone.now()

    @transition(
        field=lifecycle_status,
        source=[Status.SUBMITTED, Status.UNDER_VETTING],
        target=Status.REVISIONS_REQUESTED,
    )
    def request_revisions(self, *, by_user, feedback: str) -> None:
        self.feedback_to_innovator = feedback
        self.decided_at = timezone.now()
        self.decided_by = by_user

    @transition(
        field=lifecycle_status,
        source=Status.UNDER_VETTING,
        target=Status.APPROVED,
    )
    def approve(self, *, by_user, feedback: str = "") -> None:
        self.feedback_to_innovator = feedback
        self.decided_at = timezone.now()
        self.decided_by = by_user
        self.rejection_reason = ""

    @transition(
        field=lifecycle_status,
        source=[Status.SUBMITTED, Status.UNDER_VETTING],
        target=Status.REJECTED,
    )
    def reject(self, *, by_user, reason: str) -> None:
        self.rejection_reason = reason
        self.decided_at = timezone.now()
        self.decided_by = by_user

    def onboard(self, *, by_user) -> None:
        """Publish to the public catalogue. Idempotent. APPROVED only."""
        if self.lifecycle_status != self.Status.APPROVED:
            raise ValueError(
                f"Innovation must be APPROVED before onboard(); got {self.lifecycle_status}."
            )
        if not self.is_public:
            self.is_public = True
            self.published_at = timezone.now()

    @transition(
        field=lifecycle_status,
        source=[
            Status.DRAFT,
            Status.SUBMITTED,
            Status.UNDER_VETTING,
            Status.REVISIONS_REQUESTED,
        ],
        target=Status.WITHDRAWN,
    )
    def withdraw(self, *, by_user, reason: str = "") -> None:
        self.withdrawn_reason = reason


class EvaluatorAssignment(models.Model):
    """Assignment of an evaluator to an Innovation for a vetting round."""

    class Status(models.TextChoices):
        ASSIGNED = "assigned", "Assigned"
        IN_PROGRESS = "in_progress", "In progress"
        COMPLETED = "completed", "Completed"
        RECUSED = "recused", "Recused"

    innovation = models.ForeignKey(
        Innovation,
        on_delete=models.CASCADE,
        related_name="evaluator_assignments",
    )
    evaluator = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name="smehub_evaluator_assignments",
    )
    assigned_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_evaluator_assignments_made",
    )
    assigned_at = models.DateTimeField(default=timezone.now)
    due_at = models.DateTimeField(null=True, blank=True)
    status = models.CharField(
        max_length=16,
        choices=Status.choices,
        default=Status.ASSIGNED,
        db_index=True,
    )
    completed_at = models.DateTimeField(null=True, blank=True)
    recusal_reason = models.TextField(blank=True)

    class Meta:
        ordering = ["-assigned_at"]
        constraints = [
            models.UniqueConstraint(
                fields=["innovation", "evaluator"],
                name="uniq_smehub_innovation_evaluator",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.evaluator} → {self.innovation.innovation_id} [{self.status}]"


class VettingRecord(models.Model):
    """One per innovation per vetting round.

    A second round opens automatically when an innovation transitions
    REVISIONS_REQUESTED → SUBMITTED → UNDER_VETTING again.
    """

    class Recommendation(models.TextChoices):
        APPROVE = "approve", "Approve"
        REJECT = "reject", "Reject"
        REVISE = "revise", "Request revisions"

    innovation = models.ForeignKey(
        Innovation,
        on_delete=models.CASCADE,
        related_name="vetting_records",
    )
    round_no = models.PositiveSmallIntegerField(default=1)
    started_at = models.DateTimeField(default=timezone.now)
    closed_at = models.DateTimeField(null=True, blank=True)
    recommendation = models.CharField(
        max_length=16,
        choices=Recommendation.choices,
        blank=True,
    )
    summary = models.TextField(blank=True)
    closed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    class Meta:
        ordering = ["innovation_id", "round_no"]
        constraints = [
            models.UniqueConstraint(
                fields=["innovation", "round_no"],
                name="uniq_smehub_vetting_round",
            ),
        ]

    def __str__(self) -> str:
        return f"Round {self.round_no} — {self.innovation.innovation_id}"

    @property
    def average_score(self):
        from decimal import Decimal

        scores = list(self.scores.all().values_list("score", flat=True))
        if not scores:
            return None
        return (Decimal(sum(scores)) / Decimal(len(scores))).quantize(Decimal("0.01"))

    @property
    def evaluator_count(self) -> int:
        return self.scores.values("evaluator").distinct().count()

    @property
    def dimension_breakdown(self) -> dict:
        from collections import defaultdict
        from decimal import Decimal

        buckets: dict[str, list[int]] = defaultdict(list)
        for score in self.scores.all():
            buckets[score.dimension].append(int(score.score))
        return {
            dim: float((Decimal(sum(vals)) / Decimal(len(vals))).quantize(Decimal("0.01")))
            for dim, vals in buckets.items()
        }


class VettingScore(models.Model):
    """One score per (vetting_record, evaluator, dimension)."""

    class Dimension(models.TextChoices):
        TECHNICAL_ROBUSTNESS = "technical_robustness", "Technical robustness"
        MARKET_POTENTIAL = "market_potential", "Market potential"
        SCALABILITY = "scalability", "Scalability"
        REGULATORY_ALIGNMENT = "regulatory_alignment", "Regulatory alignment"
        TEAM_CAPACITY = "team_capacity", "Team capacity"

    vetting_record = models.ForeignKey(
        VettingRecord,
        on_delete=models.CASCADE,
        related_name="scores",
    )
    evaluator = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name="smehub_vetting_scores",
    )
    dimension = models.CharField(max_length=32, choices=Dimension.choices)
    score = models.PositiveSmallIntegerField(help_text="1 (weak) – 5 (strong).")
    comments = models.TextField(blank=True)
    submitted_at = models.DateTimeField(default=timezone.now)

    class Meta:
        ordering = ["dimension"]
        constraints = [
            models.UniqueConstraint(
                fields=["vetting_record", "evaluator", "dimension"],
                name="uniq_smehub_vetting_score",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.evaluator} {self.dimension}={self.score}"


# ---------------------------------------------------------------------------
# Imported content & relationships  (EHC migration)
# ---------------------------------------------------------------------------


class Faq(models.Model):
    """Help/FAQ entry (imported `faqs` table)."""

    question = models.TextField()
    answer = models.TextField(blank=True)
    is_published = models.BooleanField(
        default=False,
        help_text="Show on the public FAQ page (imported `display_on_frontend`).",
    )
    order = models.PositiveIntegerField(default=0, db_index=True)
    external_ref = models.PositiveBigIntegerField(
        null=True, blank=True, unique=True, db_index=True
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["order", "id"]

    def __str__(self) -> str:
        return (self.question or "")[:80]


class NewsletterSubscriber(models.Model):
    """Newsletter / mailing-list subscriber (imported `email_subscribers`)."""

    class Status(models.TextChoices):
        SUBSCRIBED = "subscribed", "Subscribed"
        UNSUBSCRIBED = "unsubscribed", "Unsubscribed"

    email = models.EmailField(unique=True, db_index=True)
    status = models.CharField(
        max_length=16, choices=Status.choices, default=Status.SUBSCRIBED, db_index=True
    )
    subscribed_at = models.DateTimeField(null=True, blank=True)
    external_ref = models.PositiveBigIntegerField(
        null=True, blank=True, unique=True, db_index=True
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["email"]

    def __str__(self) -> str:
        return self.email


class MeetingRequest(models.Model):
    """Direct meeting request from a user to a Mentor or Business
    (imported `meeting_requests`; polymorphic recipient)."""

    class RecipientType(models.TextChoices):
        MENTOR = "mentor", "Mentor"
        BUSINESS = "business", "Business"

    class Status(models.TextChoices):
        REQUEST = "request", "Requested"
        ACCEPTED = "accepted", "Accepted"
        REJECTED = "rejected", "Rejected"
        CANCELLED = "cancelled", "Cancelled"

    requester = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="smehub_meeting_requests",
    )
    recipient_type = models.CharField(max_length=16, choices=RecipientType.choices)
    recipient_content_type = models.ForeignKey(
        ContentType, on_delete=models.CASCADE, null=True, blank=True
    )
    recipient_object_id = models.PositiveBigIntegerField(null=True, blank=True)
    recipient = GenericForeignKey("recipient_content_type", "recipient_object_id")
    meeting_time = models.DateTimeField(null=True, blank=True)
    status = models.CharField(
        max_length=16, choices=Status.choices, default=Status.REQUEST, db_index=True
    )
    reason = models.TextField(blank=True)
    agenda = models.TextField(blank=True)
    meeting_link = models.URLField(max_length=255, blank=True)
    external_ref = models.PositiveBigIntegerField(
        null=True, blank=True, unique=True, db_index=True
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self) -> str:
        return f"Meeting request {self.requester_id} → {self.recipient_type} #{self.recipient_object_id}"


class CommunityDiscussion(models.Model):
    """Archived community-forum thread (imported `chatter_discussion`).

    The legacy SME-Hub ran a small peer Q&A forum; IILMP preserves the
    threads read-only. created_at is backdated to the source post date.
    """

    title = models.CharField(max_length=255)
    category = models.CharField(max_length=120, blank=True)
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_community_discussions",
    )
    external_ref = models.PositiveBigIntegerField(
        null=True, blank=True, unique=True, db_index=True
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self) -> str:
        return self.title


class CommunityPost(models.Model):
    """Archived reply within a CommunityDiscussion (imported `chatter_post`)."""

    discussion = models.ForeignKey(
        CommunityDiscussion, on_delete=models.CASCADE, related_name="posts"
    )
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="smehub_community_posts",
    )
    body = models.TextField(help_text="Sanitised at render time via |safe_html.")
    external_ref = models.PositiveBigIntegerField(
        null=True, blank=True, unique=True, db_index=True
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["created_at"]

    def __str__(self) -> str:
        return f"Post #{self.pk} in {self.discussion_id}"
