"""Sales request schemas."""
import base64
import binascii
from datetime import date
from decimal import Decimal
from typing import Optional, List
from uuid import UUID
from pydantic import BaseModel, EmailStr, Field, field_validator

from src.core.constants import OpportunityStage
from src.core.validators import validate_phone


class OpportunityCreateRequest(BaseModel):
    family_name: str
    care_type: Optional[str] = None
    contact_phone: Optional[str] = None
    contact_email: Optional[str] = None
    section_id: Optional[UUID] = None
    plot_id: Optional[UUID] = None
    estimated_value: Optional[Decimal] = Field(default=None, ge=0)
    assigned_to: Optional[UUID] = None
    next_action: Optional[str] = None
    notes: Optional[str] = None

    @field_validator('contact_phone', mode='before')
    @classmethod
    def _validate_phone(cls, v: Optional[str]) -> Optional[str]:
        return validate_phone(v)


class OpportunityUpdateRequest(BaseModel):
    family_name: Optional[str] = None
    care_type: Optional[str] = None
    contact_phone: Optional[str] = None
    contact_email: Optional[str] = None
    section_id: Optional[UUID] = None
    plot_id: Optional[UUID] = None
    estimated_value: Optional[Decimal] = Field(default=None, ge=0)
    assigned_to: Optional[UUID] = None
    # Stage must be a valid enum value; clients cannot inject arbitrary strings.
    # The LOST stage is only set server-side via the DELETE endpoint (soft-delete flow).
    stage: Optional[OpportunityStage] = None
    next_action: Optional[str] = None
    notes: Optional[str] = None

    @field_validator('contact_phone', mode='before')
    @classmethod
    def _validate_phone(cls, v: Optional[str]) -> Optional[str]:
        return validate_phone(v)


class OpportunityLostRequest(BaseModel):
    lost_reason: Optional[str] = None


class ProposalLineItemRequest(BaseModel):
    description: str
    # Both quantity and unit_price must be non-negative to prevent negative totals.
    quantity: Decimal = Field(default=Decimal("1"), gt=0)
    unit_price: Decimal = Field(ge=0)


class ProposalCreateRequest(BaseModel):
    opportunity_id: Optional[UUID] = None
    to_email: str
    cc_email: Optional[str] = None
    subject: str
    cover_note: Optional[str] = None
    # Cap at 365 days to prevent absurdly far-future expiry dates.
    valid_days: int = Field(default=30, ge=1, le=365)
    sales_owner_id: Optional[UUID] = None
    line_items: List[ProposalLineItemRequest] = []


class ProposalSendRequest(BaseModel):
    pass


# Legacy contract schemas — kept for backward compatibility
class ContractLineItemRequest(BaseModel):
    description: str
    quantity: Decimal = Decimal("1")
    unit_price: Decimal
    discount: Decimal = Decimal("0")


class ContractCreateRequest(BaseModel):
    opportunity_id: Optional[UUID] = None
    record_id: Optional[UUID] = None
    plot_id: Optional[UUID] = None
    signatory_name: str
    signatory_email: Optional[str] = None
    sign_date: Optional[date] = None
    notes: Optional[str] = None
    line_items: List[ContractLineItemRequest] = []


class ContractUpdateRequest(BaseModel):
    status: Optional[str] = None
    signatory_name: Optional[str] = None
    sign_date: Optional[date] = None
    notes: Optional[str] = None


class ContractLineItemCreateRequest(BaseModel):
    description: str
    fee_type: Optional[str] = None
    quantity: Decimal = Field(default=Decimal("1"), gt=0)
    unit_price: Decimal = Field(ge=0)


_ALLOWED_CONTRACT_TYPES = {"pre_need", "at_need"}
_ALLOWED_PAYMENT_PLAN_TYPES = {"full", "installment"}
# Maximum number of allowed keys in payment_schedule to prevent JSONB bloat
_MAX_PAYMENT_SCHEDULE_KEYS = 50


class ContractWizardCreateRequest(BaseModel):
    opportunity_id: Optional[UUID] = None
    plot_id: Optional[UUID] = None
    section_id: Optional[UUID] = None
    contract_type: str  # "pre_need" or "at_need"
    purchaser_name: str = Field(min_length=1, max_length=255)
    purchaser_email: Optional[EmailStr] = None
    purchaser_phone: Optional[str] = Field(default=None, max_length=30)
    purchaser_address: Optional[str] = Field(default=None, max_length=500)
    payment_plan_type: str = "full"
    deposit_amount: Optional[Decimal] = Field(default=None, ge=0)
    payment_schedule: Optional[dict] = None
    line_items: List[ContractLineItemCreateRequest] = []

    @field_validator('purchaser_phone', mode='before')
    @classmethod
    def _validate_phone(cls, v: Optional[str]) -> Optional[str]:
        return validate_phone(v)

    @field_validator("contract_type")
    @classmethod
    def validate_contract_type(cls, v: str) -> str:
        if v not in _ALLOWED_CONTRACT_TYPES:
            raise ValueError(f"contract_type must be one of: {_ALLOWED_CONTRACT_TYPES}")
        return v

    @field_validator("payment_plan_type")
    @classmethod
    def validate_payment_plan_type(cls, v: str) -> str:
        if v not in _ALLOWED_PAYMENT_PLAN_TYPES:
            raise ValueError(f"payment_plan_type must be one of: {_ALLOWED_PAYMENT_PLAN_TYPES}")
        return v

    @field_validator("payment_schedule")
    @classmethod
    def validate_payment_schedule(cls, v: Optional[dict]) -> Optional[dict]:
        """Reject schedules that are oversized or contain non-primitive values."""
        if v is None:
            return v
        if len(v) > _MAX_PAYMENT_SCHEDULE_KEYS:
            raise ValueError(
                f"payment_schedule may contain at most {_MAX_PAYMENT_SCHEDULE_KEYS} keys"
            )
        allowed_value_types = (str, int, float, bool, type(None))
        for key, val in v.items():
            if not isinstance(key, str):
                raise ValueError("payment_schedule keys must be strings")
            if len(key) > 100:
                raise ValueError("payment_schedule key names must not exceed 100 characters")
            if not isinstance(val, allowed_value_types):
                raise ValueError(
                    f"payment_schedule values must be scalar (str, int, float, bool, null); "
                    f"got {type(val).__name__} for key '{key}'"
                )
            if isinstance(val, str) and len(val) > 2000:
                raise ValueError(
                    f"payment_schedule string values must not exceed 2000 characters (key '{key}')"
                )
        return v


class ContractSignRequest(BaseModel):
    # 2 MB ceiling expressed as base64-encoded bytes.
    # A typical 400×200 canvas PNG is ~15 KB; 2 MB gives generous headroom
    # while blocking payload-amplification attacks.
    # Max base64 length for 2 MB binary = ceil(2_097_152 / 3) * 4 = 2_796_204
    purchaser_signature_b64: str = Field(min_length=10, max_length=2_796_204)
    witness_name: Optional[str] = Field(default=None, max_length=255)

    @field_validator("purchaser_signature_b64")
    @classmethod
    def validate_base64_data_uri(cls, v: str) -> str:
        """Reject payloads that are not a valid PNG/JPEG data URI or bare base64."""
        # Accept either a data URI (data:image/png;base64,... / data:image/jpeg;base64,...)
        # or raw base64 — both are produced by the SignatureCanvas component.
        raw = v
        if v.startswith("data:"):
            if not (v.startswith("data:image/png;base64,") or v.startswith("data:image/jpeg;base64,")):
                raise ValueError("Signature must be a PNG or JPEG data URI")
            raw = v.split(",", 1)[1]
        try:
            base64.b64decode(raw, validate=True)
        except (binascii.Error, ValueError) as exc:
            raise ValueError("purchaser_signature_b64 is not valid base64") from exc
        return v
