from typing import Annotated, Optional, List, Dict, Any
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator, BeforeValidator
from datetime import date, datetime
from decimal import Decimal
from utils.utils import LabTestStatus

StrId = Annotated[str, BeforeValidator(str)]


# ======================== Vitalsign Schemas ========================
class VitalsignBase(BaseModel):
    patient_id: str = Field(..., max_length=36)
    recorded_by_user_id: str = Field(..., max_length=36)
    recorded_at: datetime
    systolic_bp: Optional[int] = None
    diastolic_bp: Optional[int] = None
    pulse_rate: Optional[int] = Field(None, ge=40, le=200)
    temperature_celsius: Optional[Decimal] = Field(None, max_digits=4, decimal_places=2)
    respiratory_rate: Optional[int] = None
    weight_kg: Optional[Decimal] = Field(None, max_digits=6, decimal_places=2)
    height_cm: Optional[Decimal] = Field(None, max_digits=6, decimal_places=2)
    oxygen_saturation: Optional[int] = Field(None, ge=70, le=100)
    notes: Optional[str] = None

    @model_validator(mode="after")
    def validate_blood_pressure(self):
        if self.systolic_bp is not None and self.diastolic_bp is not None:
            if self.systolic_bp <= self.diastolic_bp:
                raise ValueError("Systolic blood pressure must be greater than diastolic blood pressure")
        return self

    @model_validator(mode="after")
    def validate_temperature_range(self):
        if self.temperature_celsius is not None:
            celsius_min = Decimal("35.0")
            celsius_max = Decimal("43.3")
            if not (celsius_min <= self.temperature_celsius <= celsius_max):
                raise ValueError("Temperature must be within valid range (95°F - 110°F / 35°C - 43.3°C)")
        return self


class VitalsignCreate(VitalsignBase):
    pass


class VitalsignUpdate(BaseModel):
    patient_id: Optional[str] = Field(None, max_length=36)
    recorded_by_user_id: Optional[str] = Field(None, max_length=36)
    recorded_at: Optional[datetime] = None
    systolic_bp: Optional[int] = None
    diastolic_bp: Optional[int] = None
    pulse_rate: Optional[int] = Field(None, ge=40, le=200)
    temperature_celsius: Optional[Decimal] = Field(None, max_digits=4, decimal_places=2)
    respiratory_rate: Optional[int] = None
    weight_kg: Optional[Decimal] = Field(None, max_digits=6, decimal_places=2)
    height_cm: Optional[Decimal] = Field(None, max_digits=6, decimal_places=2)
    oxygen_saturation: Optional[int] = Field(None, ge=70, le=100)
    notes: Optional[str] = None

    @model_validator(mode="after")
    def validate_blood_pressure(self):
        if self.systolic_bp is not None and self.diastolic_bp is not None:
            if self.systolic_bp <= self.diastolic_bp:
                raise ValueError("Systolic blood pressure must be greater than diastolic blood pressure")
        return self

    @model_validator(mode="after")
    def validate_temperature_range(self):
        if self.temperature_celsius is not None:
            celsius_min = Decimal("35.0")
            celsius_max = Decimal("43.3")
            if not (celsius_min <= self.temperature_celsius <= celsius_max):
                raise ValueError("Temperature must be within valid range (95°F - 110°F / 35°C - 43.3°C)")
        return self


class VitalsignResponse(BaseModel):
    id: StrId
    patient_id: StrId
    recorded_by_user_id: StrId
    recorded_at: datetime
    systolic_bp: Optional[int] = None
    diastolic_bp: Optional[int] = None
    pulse_rate: Optional[int] = None
    temperature_celsius: Optional[Decimal] = None
    respiratory_rate: Optional[int] = None
    weight_kg: Optional[Decimal] = None
    height_cm: Optional[Decimal] = None
    oxygen_saturation: Optional[int] = None
    notes: Optional[str] = None
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


# ======================== Consultation Schemas ========================
class ConsultationBase(BaseModel):
    appointment_id: Optional[str] = Field(None, max_length=36)
    patient_id: str = Field(..., max_length=36)
    doctor_id: str = Field(..., max_length=36)
    consultation_date: datetime
    chief_complaint: Optional[str] = None
    present_illness: Optional[str] = None
    examination_findings: Optional[str] = None
    diagnosis: Optional[str] = None
    treatment_plan: Optional[str] = None
    notes: Optional[str] = None
    follow_up_required: bool = False
    follow_up_date: Optional[date] = None

    @model_validator(mode="after")
    def validate_follow_up_date(self):
        if self.follow_up_required and self.follow_up_date is None:
            raise ValueError("Follow-up date must be provided when follow-up is required")
        return self


class ConsultationCreate(ConsultationBase):
    pass


class ConsultationUpdate(BaseModel):
    appointment_id: Optional[str] = Field(None, max_length=36)
    patient_id: Optional[str] = Field(None, max_length=36)
    doctor_id: Optional[str] = Field(None, max_length=36)
    consultation_date: Optional[datetime] = None
    chief_complaint: Optional[str] = None
    present_illness: Optional[str] = None
    examination_findings: Optional[str] = None
    diagnosis: Optional[str] = None
    treatment_plan: Optional[str] = None
    notes: Optional[str] = None
    follow_up_required: Optional[bool] = None
    follow_up_date: Optional[date] = None

    @model_validator(mode="after")
    def validate_follow_up_date(self):
        if self.follow_up_required is True and self.follow_up_date is None:
            raise ValueError("Follow-up date must be provided when follow-up is required")
        return self


class ConsultationResponse(BaseModel):
    id: StrId
    appointment_id: Optional[StrId] = None
    patient_id: StrId
    doctor_id: StrId
    consultation_date: datetime
    chief_complaint: Optional[str] = None
    present_illness: Optional[str] = None
    examination_findings: Optional[str] = None
    diagnosis: Optional[str] = None
    treatment_plan: Optional[str] = None
    notes: Optional[str] = None
    follow_up_required: bool
    follow_up_date: Optional[date] = None
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


# ======================== Prescription Schemas ========================
class PrescriptionBase(BaseModel):
    consultation_id: str = Field(..., max_length=36)
    patient_id: str = Field(..., max_length=36)
    doctor_id: str = Field(..., max_length=36)
    medication_name: str = Field(..., max_length=200)
    dosage: str = Field(..., max_length=100)
    frequency: str = Field(..., max_length=100)
    duration: str = Field(..., max_length=100)
    quantity: Optional[int] = Field(None, ge=1)
    route: Optional[str] = Field(None, max_length=50)
    special_instructions: Optional[str] = None
    is_active: bool = True
    prescribed_date: date


class PrescriptionCreate(PrescriptionBase):
    pass


class PrescriptionUpdate(BaseModel):
    consultation_id: Optional[str] = Field(None, max_length=36)
    patient_id: Optional[str] = Field(None, max_length=36)
    doctor_id: Optional[str] = Field(None, max_length=36)
    medication_name: Optional[str] = Field(None, max_length=200)
    dosage: Optional[str] = Field(None, max_length=100)
    frequency: Optional[str] = Field(None, max_length=100)
    duration: Optional[str] = Field(None, max_length=100)
    quantity: Optional[int] = Field(None, ge=1)
    route: Optional[str] = Field(None, max_length=50)
    special_instructions: Optional[str] = None
    is_active: Optional[bool] = None
    prescribed_date: Optional[date] = None


class PrescriptionResponse(BaseModel):
    id: StrId
    consultation_id: StrId
    patient_id: StrId
    doctor_id: StrId
    medication_name: str
    dosage: str
    frequency: str
    duration: str
    quantity: Optional[int] = None
    route: Optional[str] = None
    special_instructions: Optional[str] = None
    is_active: bool
    prescribed_date: date
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


# ======================== Labtest Schemas ========================
class LabtestBase(BaseModel):
    patient_id: str = Field(..., max_length=36)
    consultation_id: Optional[str] = Field(None, max_length=36)
    ordered_by_doctor_id: str = Field(..., max_length=36)
    test_name: str = Field(..., max_length=200)
    test_type: Optional[str] = Field(None, max_length=100)
    ordered_date: datetime
    sample_collected_date: Optional[datetime] = None
    result_date: Optional[datetime] = None
    result_value: Optional[str] = None
    result_unit: Optional[str] = Field(None, max_length=50)
    reference_range: Optional[str] = Field(None, max_length=100)
    status: LabTestStatus
    notes: Optional[str] = None


class LabtestCreate(LabtestBase):
    pass


class LabtestUpdate(BaseModel):
    patient_id: Optional[str] = Field(None, max_length=36)
    consultation_id: Optional[str] = Field(None, max_length=36)
    ordered_by_doctor_id: Optional[str] = Field(None, max_length=36)
    test_name: Optional[str] = Field(None, max_length=200)
    test_type: Optional[str] = Field(None, max_length=100)
    ordered_date: Optional[datetime] = None
    sample_collected_date: Optional[datetime] = None
    result_date: Optional[datetime] = None
    result_value: Optional[str] = None
    result_unit: Optional[str] = Field(None, max_length=50)
    reference_range: Optional[str] = Field(None, max_length=100)
    status: Optional[LabTestStatus] = None
    notes: Optional[str] = None


class LabtestResponse(BaseModel):
    id: StrId
    patient_id: StrId
    consultation_id: Optional[StrId] = None
    ordered_by_doctor_id: StrId
    test_name: str
    test_type: Optional[str] = None
    ordered_date: datetime
    sample_collected_date: Optional[datetime] = None
    result_date: Optional[datetime] = None
    result_value: Optional[str] = None
    result_unit: Optional[str] = None
    reference_range: Optional[str] = None
    status: LabTestStatus
    notes: Optional[str] = None
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


# ======================== Detail Schemas ========================
class PatientSummary(BaseModel):
    id: StrId
    first_name: str
    last_name: str
    date_of_birth: date

    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

    model_config = ConfigDict(from_attributes=True)


class PrescriptionSummary(BaseModel):
    id: StrId
    medication_name: str

    model_config = ConfigDict(from_attributes=True)


class LabtestSummary(BaseModel):
    id: StrId
    test_name: str
    status: LabTestStatus

    model_config = ConfigDict(from_attributes=True)


class ConsultationDetailResponse(BaseModel):
    id: StrId
    appointment_id: Optional[StrId] = None
    patient_id: StrId
    doctor_id: StrId
    consultation_date: datetime
    chief_complaint: Optional[str] = None
    present_illness: Optional[str] = None
    examination_findings: Optional[str] = None
    diagnosis: Optional[str] = None
    treatment_plan: Optional[str] = None
    notes: Optional[str] = None
    follow_up_required: bool
    follow_up_date: Optional[date] = None
    created_at: datetime
    updated_at: datetime
    patient: PatientSummary
    doctor: DoctorSummary
    appointment: Optional[AppointmentSummary] = None
    prescriptions: List[PrescriptionSummary]
    lab_tests: List[LabtestSummary]

    model_config = ConfigDict(from_attributes=True)


# ======================== Paginated Response ========================
from typing import Generic, TypeVar

T = TypeVar("T")


class PaginatedResponse(BaseModel, Generic[T]):
    items: List[T]
    total: int
    limit: int
    offset: int

    model_config = ConfigDict(from_attributes=True)


VitalsignListResponse = PaginatedResponse[VitalsignResponse]
ConsultationListResponse = PaginatedResponse[ConsultationResponse]
PrescriptionListResponse = PaginatedResponse[PrescriptionResponse]
LabtestListResponse = PaginatedResponse[LabtestResponse]