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

StrId = Annotated[str, BeforeValidator(str)]


class CalculationBase(BaseModel):
    principal: Decimal = Field(..., max_digits=15, decimal_places=2, gt=0, le=Decimal("100000000.00"))
    annual_interest_rate: Decimal = Field(..., max_digits=5, decimal_places=3, ge=0, le=Decimal("30.000"))
    loan_term_months: int = Field(..., ge=1, le=360)
    calculation_method: str = Field(default="standard_amortization", max_length=100)
    start_date: Optional[date] = None

    @field_validator("principal")
    @classmethod
    def validate_principal(cls, v):
        if v is None:
            return v
        if v <= 0:
            raise ValueError("Principal must be greater than 0")
        if v > Decimal("100000000.00"):
            raise ValueError("Principal cannot exceed $100,000,000.00")
        return v

    @field_validator("annual_interest_rate")
    @classmethod
    def validate_interest_rate(cls, v):
        if v is None:
            return v
        if v < 0:
            raise ValueError("Annual interest rate cannot be negative")
        if v > Decimal("30.000"):
            raise ValueError("Annual interest rate cannot exceed 30%")
        return v

    @field_validator("loan_term_months")
    @classmethod
    def validate_loan_term(cls, v):
        if v is None:
            return v
        if v < 1:
            raise ValueError("Loan term must be at least 1 month")
        if v > 360:
            raise ValueError("Loan term cannot exceed 360 months")
        return v


class CalculationCreate(CalculationBase):
    pass


class CalculationUpdate(BaseModel):
    principal: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, gt=0, le=Decimal("100000000.00"))
    annual_interest_rate: Optional[Decimal] = Field(None, max_digits=5, decimal_places=3, ge=0, le=Decimal("30.000"))
    loan_term_months: Optional[int] = Field(None, ge=1, le=360)
    calculation_method: Optional[str] = Field(None, max_length=100)
    start_date: Optional[date] = None

    @field_validator("principal")
    @classmethod
    def validate_principal(cls, v):
        if v is None:
            return v
        if v <= 0:
            raise ValueError("Principal must be greater than 0")
        if v > Decimal("100000000.00"):
            raise ValueError("Principal cannot exceed $100,000,000.00")
        return v

    @field_validator("annual_interest_rate")
    @classmethod
    def validate_interest_rate(cls, v):
        if v is None:
            return v
        if v < 0:
            raise ValueError("Annual interest rate cannot be negative")
        if v > Decimal("30.000"):
            raise ValueError("Annual interest rate cannot exceed 30%")
        return v

    @field_validator("loan_term_months")
    @classmethod
    def validate_loan_term(cls, v):
        if v is None:
            return v
        if v < 1:
            raise ValueError("Loan term must be at least 1 month")
        if v > 360:
            raise ValueError("Loan term cannot exceed 360 months")
        return v


class CalculationResponse(BaseModel):
    id: StrId
    principal: Decimal
    annual_interest_rate: Decimal
    loan_term_months: int
    monthly_payment: Decimal
    total_interest: Decimal
    total_amount_paid: Decimal
    calculation_method: str
    start_date: Optional[date]
    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, ge=0)
    payment_amount: Decimal = Field(..., max_digits=15, decimal_places=2, ge=0)
    principal_payment: Decimal = Field(..., max_digits=15, decimal_places=2, ge=0)
    interest_payment: Decimal = Field(..., max_digits=15, decimal_places=2, ge=0)
    ending_balance: Decimal = Field(..., max_digits=15, decimal_places=2, ge=0)
    cumulative_interest: 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, ge=0)
    payment_amount: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=0)
    principal_payment: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=0)
    interest_payment: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=0)
    ending_balance: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=0)
    cumulative_interest: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=0)


class AmortizationscheduleentryResponse(BaseModel):
    id: StrId
    calculation_id: StrId
    payment_number: int
    payment_date: date
    beginning_balance: Decimal
    payment_amount: Decimal
    principal_payment: Decimal
    interest_payment: Decimal
    ending_balance: Decimal
    cumulative_interest: Decimal
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


class CalculationDetailResponse(BaseModel):
    id: StrId
    principal: Decimal
    annual_interest_rate: Decimal
    loan_term_months: int
    monthly_payment: Decimal
    total_interest: Decimal
    total_amount_paid: Decimal
    calculation_method: str
    start_date: Optional[date]
    created_at: datetime
    updated_at: datetime
    amortization_schedule_entries: List[AmortizationscheduleentryResponse]

    model_config = ConfigDict(from_attributes=True)


class PaginatedCalculationResponse(BaseModel):
    items: List[CalculationResponse]
    total: int
    limit: int
    offset: int

    model_config = ConfigDict(from_attributes=True)