from __future__ import annotations

from datetime import date, datetime
from typing import Optional

from pydantic import BaseModel, ConfigDict, EmailStr, field_validator, model_validator

from app.modules.clients.constants import (
    CLIENT_CATEGORY_OPTIONS,
    CLIENT_SORT_FIELDS,
    CLIENT_STATUS,
    CLIENT_TYPES,
    ENGAGEMENT_MODES,
    RISK_CATEGORIES,
    CLIENT_ACCEPTANCE_STATUS,
)
from app.modules.clients.utils import GSTIN_RE, MOBILE_RE, PAN_RE, PIN_RE, TAN_RE, normalize_text, normalize_upper


class ClientBase(BaseModel):
    tenant_id: int
    branch_id: int
    partner_id: Optional[int] = None
    client_group_id: Optional[int] = None
    group_relationship: Optional[str] = None
    is_group_head: bool = False
    default_performing_partner_user_id: Optional[int] = None
    default_review_partner_user_id: Optional[int] = None
    referred_by_consultant_id: Optional[int] = None
    primary_consultant_id: Optional[int] = None
    referral_date: Optional[date] = None
    referral_reference: Optional[str] = None
    referral_status: str = "active"
    communication_routing_mode: str = "client_and_consultant"
    engagement_mode: str = "internal_managed"
    client_code: str
    client_name: str
    trade_name: Optional[str] = None
    client_type: str = "Other"
    pan: Optional[str] = None
    gstin: Optional[str] = None
    tan: Optional[str] = None
    cin_llpin: Optional[str] = None
    msme_no: Optional[str] = None
    iec_code: Optional[str] = None
    contact_person_name: Optional[str] = None
    contact_person_designation: Optional[str] = None
    mobile: Optional[str] = None
    alternate_mobile: Optional[str] = None
    email: Optional[EmailStr] = None
    alternate_email: Optional[EmailStr] = None
    address_line_1: Optional[str] = None
    address_line_2: Optional[str] = None
    city: Optional[str] = None
    state: Optional[str] = None
    pincode: Optional[str] = None
    country: Optional[str] = "India"
    status: str = "active"
    client_category: Optional[str] = None
    risk_category: Optional[str] = None
    onboarding_date: Optional[date] = None
    closing_date: Optional[date] = None
    acceptance_status: str = "pending_review"
    acceptance_required: bool = True
    independence_check_completed: bool = False
    conflict_check_completed: bool = False
    kyc_completed: bool = False
    engagement_letter_required: bool = True
    engagement_letter_received: bool = False
    acceptance_review_notes: Optional[str] = None
    acceptance_rejection_reason: Optional[str] = None
    notes: Optional[str] = None
    gst_applicable: bool = False
    income_tax_applicable: bool = False
    tds_applicable: bool = False
    roc_applicable: bool = False
    audit_applicable: bool = False
    pf_applicable: bool = False
    esi_applicable: bool = False
    professional_tax_applicable: bool = False
    payroll_applicable: bool = False
    msme_applicable: bool = False
    import_export_applicable: bool = False

    @field_validator("client_code", "client_name", mode="before")
    @classmethod
    def required_text(cls, value):
        value = normalize_text(value)
        if not value:
            raise ValueError("This field is required.")
        return value

    @field_validator(
        "trade_name", "cin_llpin", "msme_no", "iec_code", "contact_person_name", "contact_person_designation",
        "address_line_1", "address_line_2", "city", "state", "country", "client_category", "risk_category", "referral_reference", "group_relationship", "acceptance_review_notes", "acceptance_rejection_reason", "notes",
        mode="before",
    )
    @classmethod
    def clean_text(cls, value):
        return normalize_text(value)

    @field_validator("pan", "gstin", "tan", mode="before")
    @classmethod
    def uppercase_codes(cls, value):
        return normalize_upper(value)

    @field_validator("referral_status", "communication_routing_mode", mode="before")
    @classmethod
    def clean_referral_fields(cls, value):
        return (normalize_text(value) or "").lower()

    @field_validator("referral_status")
    @classmethod
    def validate_referral_status(cls, value):
        if value not in {"active", "inactive", "ended"}:
            raise ValueError("Invalid referral status.")
        return value

    @field_validator("communication_routing_mode")
    @classmethod
    def validate_communication_routing_mode(cls, value):
        if value not in {"client_direct", "consultant_primary", "client_and_consultant", "firm_only"}:
            raise ValueError("Invalid communication routing mode.")
        return value

    @field_validator("engagement_mode", mode="before")
    @classmethod
    def clean_engagement_mode(cls, value):
        value = normalize_text(value) or "internal_managed"
        return value.lower()

    @field_validator("engagement_mode")
    @classmethod
    def validate_engagement_mode(cls, value):
        if value not in ENGAGEMENT_MODES:
            raise ValueError("Invalid engagement mode.")
        return value

    @field_validator("acceptance_status", mode="before")
    @classmethod
    def clean_acceptance_status(cls, value):
        value = normalize_text(value) or "pending_review"
        return value.lower()

    @field_validator("acceptance_status")
    @classmethod
    def validate_acceptance_status(cls, value):
        if value not in CLIENT_ACCEPTANCE_STATUS:
            raise ValueError("Invalid client acceptance status.")
        return value

    @field_validator("mobile", "alternate_mobile", mode="before")
    @classmethod
    def clean_mobile(cls, value):
        value = normalize_text(value)
        if value is None:
            return None
        value = value.replace(" ", "").replace("-", "")
        if value.startswith("+91"):
            value = value[3:]
        return value

    @field_validator("pan")
    @classmethod
    def validate_pan(cls, value):
        if value and not PAN_RE.match(value):
            raise ValueError("Invalid PAN format.")
        return value

    @field_validator("gstin")
    @classmethod
    def validate_gstin(cls, value):
        if value and not GSTIN_RE.match(value):
            raise ValueError("Invalid GSTIN format.")
        return value

    @field_validator("tan")
    @classmethod
    def validate_tan(cls, value):
        if value and not TAN_RE.match(value):
            raise ValueError("Invalid TAN format.")
        return value

    @field_validator("mobile", "alternate_mobile")
    @classmethod
    def validate_mobile(cls, value):
        if value and not MOBILE_RE.match(value):
            raise ValueError("Mobile number must be a valid 10-digit Indian mobile.")
        return value

    @field_validator("pincode", mode="before")
    @classmethod
    def clean_pincode(cls, value):
        return normalize_text(value)

    @field_validator("pincode")
    @classmethod
    def validate_pincode(cls, value):
        if value and not PIN_RE.match(value):
            raise ValueError("Pincode must be a valid 6-digit code.")
        return value

    @model_validator(mode="after")
    def validate_dates_and_assignment(self):
        if self.onboarding_date and self.closing_date and self.closing_date < self.onboarding_date:
            raise ValueError("Closing date cannot be earlier than onboarding date.")
        if self.engagement_mode == "internal_managed" and not self.partner_id:
            raise ValueError("Partner is required for internal managed clients.")
        return self


class ClientCreate(ClientBase):
    pass


class ClientUpdate(BaseModel):
    tenant_id: Optional[int] = None
    branch_id: Optional[int] = None
    partner_id: Optional[int] = None
    client_group_id: Optional[int] = None
    group_relationship: Optional[str] = None
    is_group_head: Optional[bool] = None
    default_performing_partner_user_id: Optional[int] = None
    default_review_partner_user_id: Optional[int] = None
    referred_by_consultant_id: Optional[int] = None
    primary_consultant_id: Optional[int] = None
    referral_date: Optional[date] = None
    referral_reference: Optional[str] = None
    referral_status: Optional[str] = None
    communication_routing_mode: Optional[str] = None
    engagement_mode: Optional[str] = None
    client_name: Optional[str] = None
    trade_name: Optional[str] = None
    client_type: Optional[str] = None
    pan: Optional[str] = None
    gstin: Optional[str] = None
    tan: Optional[str] = None
    cin_llpin: Optional[str] = None
    msme_no: Optional[str] = None
    iec_code: Optional[str] = None
    contact_person_name: Optional[str] = None
    contact_person_designation: Optional[str] = None
    mobile: Optional[str] = None
    alternate_mobile: Optional[str] = None
    email: Optional[EmailStr] = None
    alternate_email: Optional[EmailStr] = None
    address_line_1: Optional[str] = None
    address_line_2: Optional[str] = None
    city: Optional[str] = None
    state: Optional[str] = None
    pincode: Optional[str] = None
    country: Optional[str] = None
    status: Optional[str] = None
    client_category: Optional[str] = None
    risk_category: Optional[str] = None
    onboarding_date: Optional[date] = None
    closing_date: Optional[date] = None
    acceptance_status: Optional[str] = None
    acceptance_required: Optional[bool] = None
    independence_check_completed: Optional[bool] = None
    conflict_check_completed: Optional[bool] = None
    kyc_completed: Optional[bool] = None
    engagement_letter_required: Optional[bool] = None
    engagement_letter_received: Optional[bool] = None
    acceptance_review_notes: Optional[str] = None
    acceptance_rejection_reason: Optional[str] = None
    notes: Optional[str] = None
    gst_applicable: Optional[bool] = None
    income_tax_applicable: Optional[bool] = None
    tds_applicable: Optional[bool] = None
    roc_applicable: Optional[bool] = None
    audit_applicable: Optional[bool] = None
    pf_applicable: Optional[bool] = None
    esi_applicable: Optional[bool] = None
    professional_tax_applicable: Optional[bool] = None
    payroll_applicable: Optional[bool] = None
    msme_applicable: Optional[bool] = None
    import_export_applicable: Optional[bool] = None
    model_config = ConfigDict(extra="forbid")

    @field_validator(
        "client_name", "trade_name", "cin_llpin", "msme_no", "iec_code", "contact_person_name", "contact_person_designation",
        "address_line_1", "address_line_2", "city", "state", "country", "client_category", "risk_category", "referral_reference", "group_relationship", "acceptance_review_notes", "acceptance_rejection_reason", "notes",
        mode="before",
    )
    @classmethod
    def clean_text(cls, value):
        return normalize_text(value)

    @field_validator("pan", "gstin", "tan", mode="before")
    @classmethod
    def uppercase_codes(cls, value):
        return normalize_upper(value)

    @field_validator("referral_status", "communication_routing_mode", mode="before")
    @classmethod
    def clean_optional_referral_fields(cls, value):
        value = normalize_text(value)
        return value.lower() if value else None

    @field_validator("referral_status")
    @classmethod
    def validate_optional_referral_status(cls, value):
        if value is not None and value not in {"active", "inactive", "ended"}:
            raise ValueError("Invalid referral status.")
        return value

    @field_validator("communication_routing_mode")
    @classmethod
    def validate_optional_communication_routing_mode(cls, value):
        if value is not None and value not in {"client_direct", "consultant_primary", "client_and_consultant", "firm_only"}:
            raise ValueError("Invalid communication routing mode.")
        return value

    @field_validator("engagement_mode", mode="before")
    @classmethod
    def clean_engagement_mode(cls, value):
        if value is None:
            return None
        value = normalize_text(value) or None
        return value.lower() if value else None

    @field_validator("engagement_mode")
    @classmethod
    def validate_engagement_mode(cls, value):
        if value is not None and value not in ENGAGEMENT_MODES:
            raise ValueError("Invalid engagement mode.")
        return value

    @field_validator("acceptance_status", mode="before")
    @classmethod
    def clean_acceptance_status(cls, value):
        if value is None:
            return None
        value = normalize_text(value) or None
        return value.lower() if value else None

    @field_validator("acceptance_status")
    @classmethod
    def validate_acceptance_status(cls, value):
        if value is not None and value not in CLIENT_ACCEPTANCE_STATUS:
            raise ValueError("Invalid client acceptance status.")
        return value

    @field_validator("mobile", "alternate_mobile", mode="before")
    @classmethod
    def clean_mobile(cls, value):
        value = normalize_text(value)
        if value is None:
            return None
        value = value.replace(" ", "").replace("-", "")
        if value.startswith("+91"):
            value = value[3:]
        return value

    @field_validator("pan")
    @classmethod
    def validate_pan(cls, value):
        if value and not PAN_RE.match(value):
            raise ValueError("Invalid PAN format.")
        return value

    @field_validator("gstin")
    @classmethod
    def validate_gstin(cls, value):
        if value and not GSTIN_RE.match(value):
            raise ValueError("Invalid GSTIN format.")
        return value

    @field_validator("tan")
    @classmethod
    def validate_tan(cls, value):
        if value and not TAN_RE.match(value):
            raise ValueError("Invalid TAN format.")
        return value

    @field_validator("mobile", "alternate_mobile")
    @classmethod
    def validate_mobile(cls, value):
        if value and not MOBILE_RE.match(value):
            raise ValueError("Mobile number must be a valid 10-digit Indian mobile.")
        return value

    @field_validator("pincode", mode="before")
    @classmethod
    def clean_pincode(cls, value):
        return normalize_text(value)

    @field_validator("pincode")
    @classmethod
    def validate_pincode(cls, value):
        if value and not PIN_RE.match(value):
            raise ValueError("Pincode must be a valid 6-digit code.")
        return value


class ClientOut(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    id: int
    tenant_id: int
    branch_id: int
    partner_id: Optional[int] = None
    default_performing_partner_user_id: Optional[int] = None
    default_review_partner_user_id: Optional[int] = None
    engagement_mode: str
    client_code: str
    client_name: str
    trade_name: Optional[str] = None
    client_type: str
    pan: Optional[str] = None
    gstin: Optional[str] = None
    tan: Optional[str] = None
    cin_llpin: Optional[str] = None
    msme_no: Optional[str] = None
    iec_code: Optional[str] = None
    contact_person_name: Optional[str] = None
    contact_person_designation: Optional[str] = None
    mobile: Optional[str] = None
    alternate_mobile: Optional[str] = None
    email: Optional[str] = None
    alternate_email: Optional[str] = None
    address_line_1: Optional[str] = None
    address_line_2: Optional[str] = None
    city: Optional[str] = None
    state: Optional[str] = None
    pincode: Optional[str] = None
    country: Optional[str] = None
    status: str
    client_category: Optional[str] = None
    risk_category: Optional[str] = None
    onboarding_date: Optional[date] = None
    closing_date: Optional[date] = None
    acceptance_status: str = "pending_review"
    acceptance_required: bool = True
    independence_check_completed: bool = False
    conflict_check_completed: bool = False
    kyc_completed: bool = False
    engagement_letter_required: bool = True
    engagement_letter_received: bool = False
    acceptance_review_notes: Optional[str] = None
    acceptance_rejection_reason: Optional[str] = None
    notes: Optional[str] = None
    gst_applicable: bool
    income_tax_applicable: bool
    tds_applicable: bool
    roc_applicable: bool
    audit_applicable: bool
    pf_applicable: bool
    esi_applicable: bool
    professional_tax_applicable: bool
    payroll_applicable: bool
    msme_applicable: bool
    import_export_applicable: bool
    is_active: bool
    is_archived: bool
    created_at_utc: datetime
    updated_at_utc: datetime


class ClientListRow(ClientOut):
    partner_name: Optional[str] = None
    branch_name: Optional[str] = None
    tenant_name: Optional[str] = None


class ClientAuditLogOut(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    id: int
    client_id: int
    tenant_id: int
    branch_id: int
    actor_user_id: Optional[int] = None
    action: str
    summary: str
    payload_json: Optional[dict] = None
    created_at_utc: datetime


class PaginationMeta(BaseModel):
    total: int
    page: int
    per_page: int

class ClientFilterOptions(BaseModel):
    client_types: list[str]
    client_statuses: list[str]
    client_categories: list[str]
    risk_categories: list[str]


class ClientListStats(BaseModel):
    total: int = 0
    active: int = 0
    inactive: int = 0
    archived: int = 0


class ClientListResponse(BaseModel):
    rows: list[ClientOut]
    meta: PaginationMeta
    stats: ClientListStats | None = None
    filter_options: ClientFilterOptions | None = None