"""Small, explicit contracts for the independent local prototype.

The SHS master enquiry/job relationship informs these models. They are new
contracts, not imports of the original application or its startup code.
"""
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator
import re


class Strict(BaseModel):
    model_config = ConfigDict(extra='forbid', str_strip_whitespace=True)


class Login(Strict):
    email: str = Field(max_length=180)
    password: str = Field(min_length=1, max_length=200)


class PasswordChange(Strict):
    current_password: str = Field(min_length=1, max_length=200)
    new_password: str = Field(min_length=12, max_length=200)


class Enquiry(Strict):
    name: str = Field(min_length=2, max_length=100)
    email: str = Field(default='', max_length=180)
    phone: str = Field(default='', max_length=40)
    address: str = Field(default='', max_length=250)
    service: str = Field(min_length=2, max_length=120)
    message: str = Field(min_length=5, max_length=3000)

    @field_validator('email')
    @classmethod
    def email_shape(cls, v):
        if v and not re.fullmatch(r'[^\s@]+@[^\s@]+\.[^\s@]+', v):
            raise ValueError('Enter an email address, or leave it empty.')
        return v.lower()


class JobEdit(Strict):
    version: int = Field(ge=1)
    status: Literal['new', 'site_visit', 'accepted', 'scheduled', 'in_progress', 'completed', 'on_hold', 'cancelled']
    notes: str = Field(default='', max_length=6000)
    scheduled_date: str = ''

    @field_validator('scheduled_date')
    @classmethod
    def date_shape(cls, v):
        if v:
            from datetime import date
            if date.fromisoformat(v).isoformat()!=v:
                raise ValueError('Use a YYYY-MM-DD target date.')
        return v


class Line(Strict):
    description: str = Field(min_length=1, max_length=240)
    quantity: str = Field(max_length=16)
    unit_pence: int = Field(strict=True, ge=0, le=10000000)

    @field_validator('quantity',mode='before')
    @classmethod
    def valid_quantity(cls,value):
        from decimal import Decimal
        if isinstance(value,bool) or not isinstance(value,(str,int,float)):
            raise ValueError('Use a positive quantity with up to three decimal places.')
        text=str(value).strip()
        if not re.fullmatch(r'\d{1,7}(\.\d{1,3})?',text) or not 0<Decimal(text)<=10000:
            raise ValueError('Quantity must be above zero, at most 10000 and have up to three decimal places.')
        return format(Decimal(text).normalize(),'f')

    def total_pence(self):
        from decimal import Decimal, ROUND_HALF_UP
        return int((Decimal(self.quantity)*self.unit_pence).quantize(Decimal('1'),rounding=ROUND_HALF_UP))


class Quote(Strict):
    items: list[Line] = Field(min_length=1, max_length=40)
    vat_percent: Literal[0, 5, 20] = 0
    notes: str = Field(default='', max_length=2000)
    document_date: str = ''
    valid_until: str = ''

    @field_validator('document_date', 'valid_until')
    @classmethod
    def valid_date(cls, value):
        if value:
            from datetime import date
            date.fromisoformat(value)
            if len(value) != 10:
                raise ValueError('Use YYYY-MM-DD.')
        return value


class Payment(Strict):
    amount_pence: int = Field(strict=True, gt=0, le=100000000)
    note: str = Field(default='', max_length=200)
    stage_id: str | None = Field(default=None, pattern=r'^[a-f0-9]{32}$')
    paid_on: str = ''
    method: Literal['bank', 'cash', 'card_record', 'other'] = 'other'

    @field_validator('paid_on')
    @classmethod
    def valid_date(cls, value): return Quote.valid_date(value)


class Version(Strict):
    version: int = Field(strict=True, ge=1)


from .site_models import SiteContent, SiteEdit, LegacySiteContent


class Restore(Strict):
    version: int = Field(ge=1)
    revision_id: int = Field(ge=1)


class PortalLogin(Strict):
    code: str = Field(min_length=12, max_length=100)


class Domain(Strict):
    hostname: str = Field(pattern=r'^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.test$')
