from typing import Optional, List, Annotated
from pydantic import BaseModel, Field, EmailStr, field_validator, model_validator, ConfigDict, BeforeValidator
from datetime import date, datetime
from decimal import Decimal

StrId = Annotated[str, BeforeValidator(str)]


class PatientBase(BaseModel):
    patient_number: str = Field(..., max_length=50)
    first_name: str = Field(..., max_length=100)
    last_name: str = Field(..., max_length=100)
    date_of_birth: date
    gender: str = Field(..., max_length=20)
    phone: Optional[str] = Field(None, max_length=20)
    email: Optional[EmailStr] = Field(None, max_length=100)
    address: Optional[str] = None
    city: Optional[str] = Field(None, max_length=100)
    state: Optional[str] = Field(None, max_length=100)
    postal_code: Optional[str] = Field(None, max_length=20)
    country: Optional[str] = Field(None, max_length=100)
    emergency_contact_name: Optional[str] = Field(None, max_length=200)
    emergency_contact_phone: Optional[str] = Field(None, max_length=20)
    emergency_contact_relationship: Optional[str] = Field(None, max_length=100)
    insurance_provider_id: Optional[str] = Field(None, max_length=36)
    insurance_policy_number: Optional[str] = Field(None, max_length=100)

    @field_validator("email", mode="before")
    @classmethod
    def normalize_email(cls, v):
        if v is None:
            return v
        return v.strip().lower()

    @field_validator("patient_number", "first_name", "last_name", mode="before")
    @classmethod
    def strip_strings(cls, v):
        if v is None:
            return v
        return v.strip()

    @field_validator("gender")
    @classmethod
    def validate_gender(cls, v):
        if v not in ("male", "female", "other"):
            raise ValueError("gender must be one of: male, female, other")
        return v

    @model_validator(mode="after")
    def validate_dob_not_future(self):
        if self.date_of_birth and self.date_of_birth > date.today():
            raise ValueError("date_of_birth cannot be in the future")
        return self


class PatientCreate(PatientBase):
    pass


class PatientUpdate(BaseModel):
    patient_number: Optional[str] = Field(None, max_length=50)
    first_name: Optional[str] = Field(None, max_length=100)
    last_name: Optional[str] = Field(None, max_length=100)
    date_of_birth: Optional[date] = None
    gender: Optional[str] = Field(None, max_length=20)
    phone: Optional[str] = Field(None, max_length=20)
    email: Optional[EmailStr] = Field(None, max_length=100)
    address: Optional[str] = None
    city: Optional[str] = Field(None, max_length=100)
    state: Optional[str] = Field(None, max_length=100)
    postal_code: Optional[str] = Field(None, max_length=20)
    country: Optional[str] = Field(None, max_length=100)
    emergency_contact_name: Optional[str] = Field(None, max_length=200)
    emergency_contact_phone: Optional[str] = Field(None, max_length=20)
    emergency_contact_relationship: Optional[str] = Field(None, max_length=100)
    insurance_provider_id: Optional[str] = Field(None, max_length=36)
    insurance_policy_number: Optional[str] = Field(None, max_length=100)

    @field_validator("email", mode="before")
    @classmethod
    def normalize_email(cls, v):
        if v is None:
            return v
        return v.strip().lower()

    @field_validator("patient_number", "first_name", "last_name", mode="before")
    @classmethod
    def strip_strings(cls, v):
        if v is None:
            return v
        return v.strip()

    @field_validator("gender")
    @classmethod
    def validate_gender(cls, v):
        if v is None:
            return v
        if v not in ("male", "female", "other"):
            raise ValueError("gender must be one of: male, female, other")
        return v

    @model_validator(mode="after")
    def validate_dob_not_future(self):
        if self.date_of_birth and self.date_of_birth > date.today():
            raise ValueError("date_of_birth cannot be in the future")
        return self


class PatientResponse(BaseModel):
    id: StrId
    patient_number: str
    first_name: str
    last_name: str
    date_of_birth: date
    gender: str
    phone: Optional[str]
    email: Optional[str]
    address: Optional[str]
    city: Optional[str]
    state: Optional[str]
    postal_code: Optional[str]
    country: Optional[str]
    emergency_contact_name: Optional[str]
    emergency_contact_phone: Optional[str]
    emergency_contact_relationship: Optional[str]
    insurance_provider_id: Optional[StrId]
    insurance_policy_number: Optional[str]
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


class MedicalrecordBase(BaseModel):
    patient_id: str = Field(..., max_length=36)
    blood_type: Optional[str] = Field(None, max_length=10)
    allergies: Optional[str] = None
    chronic_conditions: Optional[str] = None
    family_medical_history: Optional[str] = None
    surgical_history: Optional[str] = None
    immunization_history: Optional[str] = None
    notes: Optional[str] = None


class MedicalrecordCreate(MedicalrecordBase):
    pass


class MedicalrecordUpdate(BaseModel):
    patient_id: Optional[str] = Field(None, max_length=36)
    blood_type: Optional[str] = Field(None, max_length=10)
    allergies: Optional[str] = None
    chronic_conditions: Optional[str] = None
    family_medical_history: Optional[str] = None
    surgical_history: Optional[str] = None
    immunization_history: Optional[str] = None
    notes: Optional[str] = None


class MedicalrecordResponse(BaseModel):
    id: StrId
    patient_id: StrId
    blood_type: Optional[str]
    allergies: Optional[str]
    chronic_conditions: Optional[str]
    family_medical_history: Optional[str]
    surgical_history: Optional[str]
    immunization_history: Optional[str]
    notes: Optional[str]
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


class InsuranceProviderSummary(BaseModel):
    id: StrId
    name: str

    model_config = ConfigDict(from_attributes=True)


class DepartmentSummary(BaseModel):
    id: StrId
    name: str

    model_config = ConfigDict(from_attributes=True)


class DoctorSummary(BaseModel):
    id: StrId
    first_name: str
    last_name: str

    model_config = ConfigDict(from_attributes=True)


class AppointmentSummary(BaseModel):
    id: StrId
    appointment_date: date
    appointment_time: str
    status: str
    doctor: DoctorSummary
    department: Optional[DepartmentSummary]

    model_config = ConfigDict(from_attributes=True)


class PatientDetailResponse(BaseModel):
    id: StrId
    patient_number: str
    first_name: str
    last_name: str
    date_of_birth: date
    gender: str
    phone: Optional[str]
    email: Optional[str]
    address: Optional[str]
    city: Optional[str]
    state: Optional[str]
    postal_code: Optional[str]
    country: Optional[str]
    emergency_contact_name: Optional[str]
    emergency_contact_phone: Optional[str]
    emergency_contact_relationship: Optional[str]
    insurance_provider_id: Optional[StrId]
    insurance_policy_number: Optional[str]
    created_at: datetime
    updated_at: datetime
    insurance_provider: Optional[InsuranceProviderSummary]
    medical_record: Optional[MedicalrecordResponse]
    appointments: List[AppointmentSummary]

    model_config = ConfigDict(from_attributes=True)


class PaginatedPatientResponse(BaseModel):
    items: List[PatientResponse]
    total: int
    limit: int
    offset: int

    model_config = ConfigDict(from_attributes=True)