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

StrId = Annotated[str, BeforeValidator(str)]


class LoancalculationBase(BaseModel):
    principal: Decimal = Field(..., max_digits=15, decimal_places=2, gt=0)
    annual_interest_rate: Decimal = Field(..., max_digits=5, decimal_places=2, ge=0)
    loan_term_months: int = Field(..., gt=0)
    monthly_payment: Decimal = Field(..., max_digits=15, decimal_places=2)
    total_payment: Decimal = Field(..., max_digits=15, decimal_places=2)
    total_interest: Decimal = Field(..., max_digits=15, decimal_places=2)
    session_id: str = Field(..., max_length=255)

    @field_validator("session_id")
    @classmethod
    def validate_session_id(cls, v):
        if v is None:
            return v
        v = v.strip()
        if not v:
            raise ValueError("session_id must be a non-empty string")
        return v


class LoancalculationCreate(LoancalculationBase):
    pass


class LoancalculationUpdate(BaseModel):
    principal: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, gt=0)
    annual_interest_rate: Optional[Decimal] = Field(None, max_digits=5, decimal_places=2, ge=0)
    loan_term_months: Optional[int] = Field(None, gt=0)
    monthly_payment: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    total_payment: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    total_interest: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    session_id: Optional[str] = Field(None, max_length=255)

    @field_validator("session_id")
    @classmethod
    def validate_session_id(cls, v):
        if v is None:
            return v
        v = v.strip()
        if not v:
            raise ValueError("session_id must be a non-empty string")
        return v


class LoancalculationResponse(LoancalculationBase):
    id: StrId
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


class AmortizationscheduleentryBase(BaseModel):
    calculation_id: str = Field(..., max_length=36)
    payment_number: int = Field(..., gt=0)
    payment_date: date
    beginning_balance: Decimal = Field(..., max_digits=15, decimal_places=2)
    payment_amount: Decimal = Field(..., max_digits=15, decimal_places=2)
    principal_portion: Decimal = Field(..., max_digits=15, decimal_places=2)
    interest_portion: Decimal = Field(..., max_digits=15, decimal_places=2)
    ending_balance: Decimal = Field(..., max_digits=15, decimal_places=2, ge=0)


class AmortizationscheduleentryCreate(AmortizationscheduleentryBase):
    pass


class AmortizationscheduleentryUpdate(BaseModel):
    calculation_id: Optional[str] = Field(None, max_length=36)
    payment_number: Optional[int] = Field(None, gt=0)
    payment_date: Optional[date] = None
    beginning_balance: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    payment_amount: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    principal_portion: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    interest_portion: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    ending_balance: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=0)


class AmortizationscheduleentryResponse(AmortizationscheduleentryBase):
    id: StrId
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


class LoancalculationDetailResponse(LoancalculationResponse):
    amortization_schedule_entries: List[AmortizationscheduleentryResponse] = []

    model_config = ConfigDict(from_attributes=True)


class PaginatedLoancalculationResponse(BaseModel):
    items: List[LoancalculationResponse]
    total: int
    limit: int
    offset: int

    model_config = ConfigDict(from_attributes=True)


class PaginatedAmortizationscheduleentryResponse(BaseModel):
    items: List[AmortizationscheduleentryResponse]
    total: int
    limit: int
    offset: int

    model_config = ConfigDict(from_attributes=True)