from decimal import Decimal
from datetime import date, datetime
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_amount: Decimal = Field(..., max_digits=15, decimal_places=2, ge=Decimal("1"), le=Decimal("100000000"))
    annual_interest_rate: Decimal = Field(..., max_digits=6, decimal_places=4, ge=Decimal("0.01"), le=Decimal("99.99"))
    loan_term_months: int = Field(..., ge=1, le=600)

    @field_validator("principal_amount", mode="before")
    @classmethod
    def validate_principal_amount(cls, v):
        if v is None:
            return v
        val = Decimal(str(v))
        if val < Decimal("1") or val > Decimal("100000000"):
            raise ValueError("Principal amount must be between 1 and 100,000,000")
        return val

    @field_validator("annual_interest_rate", mode="before")
    @classmethod
    def validate_annual_interest_rate(cls, v):
        if v is None:
            return v
        val = Decimal(str(v))
        if val < Decimal("0.01") or val > Decimal("99.99"):
            raise ValueError("Annual interest rate must be between 0.01 and 99.99")
        return val

    @field_validator("loan_term_months", mode="before")
    @classmethod
    def validate_loan_term_months(cls, v):
        if v is None:
            return v
        val = int(v)
        if val < 1 or val > 600:
            raise ValueError("Loan term months must be between 1 and 600")
        return val


class LoancalculationCreate(LoancalculationBase):
    pass


class LoancalculationUpdate(BaseModel):
    principal_amount: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=Decimal("1"), le=Decimal("100000000"))
    annual_interest_rate: Optional[Decimal] = Field(None, max_digits=6, decimal_places=4, ge=Decimal("0.01"), le=Decimal("99.99"))
    loan_term_months: Optional[int] = Field(None, ge=1, le=600)
    monthly_payment: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    total_amount_paid: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    total_interest_paid: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)

    @field_validator("principal_amount", mode="before")
    @classmethod
    def validate_principal_amount(cls, v):
        if v is None:
            return v
        val = Decimal(str(v))
        if val < Decimal("1") or val > Decimal("100000000"):
            raise ValueError("Principal amount must be between 1 and 100,000,000")
        return val

    @field_validator("annual_interest_rate", mode="before")
    @classmethod
    def validate_annual_interest_rate(cls, v):
        if v is None:
            return v
        val = Decimal(str(v))
        if val < Decimal("0.01") or val > Decimal("99.99"):
            raise ValueError("Annual interest rate must be between 0.01 and 99.99")
        return val

    @field_validator("loan_term_months", mode="before")
    @classmethod
    def validate_loan_term_months(cls, v):
        if v is None:
            return v
        val = int(v)
        if val < 1 or val > 600:
            raise ValueError("Loan term months must be between 1 and 600")
        return val


class LoancalculationResponse(BaseModel):
    id: StrId
    principal_amount: Decimal
    annual_interest_rate: Decimal
    loan_term_months: int
    monthly_payment: Decimal
    total_amount_paid: Decimal
    total_interest_paid: Decimal
    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: Optional[date] = None
    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)
    remaining_balance: Decimal = Field(..., max_digits=15, decimal_places=2, ge=Decimal("0"))
    cumulative_interest: Decimal = Field(..., max_digits=15, decimal_places=2, ge=Decimal("0"))
    cumulative_principal: Decimal = Field(..., max_digits=15, decimal_places=2, ge=Decimal("0"))

    @field_validator("payment_number", mode="before")
    @classmethod
    def validate_payment_number(cls, v):
        if v is None:
            return v
        val = int(v)
        if val <= 0:
            raise ValueError("Payment number must be greater than 0")
        return val

    @field_validator("remaining_balance", mode="before")
    @classmethod
    def validate_remaining_balance(cls, v):
        if v is None:
            return v
        val = Decimal(str(v))
        if val < Decimal("0"):
            raise ValueError("Remaining balance must be greater than or equal to 0")
        return val

    @field_validator("cumulative_interest", mode="before")
    @classmethod
    def validate_cumulative_interest(cls, v):
        if v is None:
            return v
        val = Decimal(str(v))
        if val < Decimal("0"):
            raise ValueError("Cumulative interest must be greater than or equal to 0")
        return val

    @field_validator("cumulative_principal", mode="before")
    @classmethod
    def validate_cumulative_principal(cls, v):
        if v is None:
            return v
        val = Decimal(str(v))
        if val < Decimal("0"):
            raise ValueError("Cumulative principal must be greater than or equal to 0")
        return val


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
    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)
    remaining_balance: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=Decimal("0"))
    cumulative_interest: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=Decimal("0"))
    cumulative_principal: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=Decimal("0"))

    @field_validator("payment_number", mode="before")
    @classmethod
    def validate_payment_number(cls, v):
        if v is None:
            return v
        val = int(v)
        if val <= 0:
            raise ValueError("Payment number must be greater than 0")
        return val

    @field_validator("remaining_balance", mode="before")
    @classmethod
    def validate_remaining_balance(cls, v):
        if v is None:
            return v
        val = Decimal(str(v))
        if val < Decimal("0"):
            raise ValueError("Remaining balance must be greater than or equal to 0")
        return val

    @field_validator("cumulative_interest", mode="before")
    @classmethod
    def validate_cumulative_interest(cls, v):
        if v is None:
            return v
        val = Decimal(str(v))
        if val < Decimal("0"):
            raise ValueError("Cumulative interest must be greater than or equal to 0")
        return val

    @field_validator("cumulative_principal", mode="before")
    @classmethod
    def validate_cumulative_principal(cls, v):
        if v is None:
            return v
        val = Decimal(str(v))
        if val < Decimal("0"):
            raise ValueError("Cumulative principal must be greater than or equal to 0")
        return val


class AmortizationscheduleentryResponse(BaseModel):
    id: StrId
    calculation_id: StrId
    payment_number: int
    payment_date: Optional[date]
    payment_amount: Decimal
    principal_portion: Decimal
    interest_portion: Decimal
    remaining_balance: Decimal
    cumulative_interest: Decimal
    cumulative_principal: Decimal
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


class AmortizationscheduleentrySummary(BaseModel):
    payment_number: int
    payment_date: Optional[date]
    payment_amount: Decimal
    principal_portion: Decimal
    interest_portion: Decimal
    remaining_balance: Decimal
    cumulative_interest: Decimal
    cumulative_principal: Decimal

    model_config = ConfigDict(from_attributes=True)


class LoancalculationWithScheduleResponse(BaseModel):
    id: StrId
    principal_amount: Decimal
    annual_interest_rate: Decimal
    loan_term_months: int
    monthly_payment: Decimal
    total_amount_paid: Decimal
    total_interest_paid: Decimal
    created_at: datetime
    updated_at: datetime
    amortization_schedule_entries: List[AmortizationscheduleentrySummary]

    model_config = ConfigDict(from_attributes=True)


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

    model_config = ConfigDict(from_attributes=True)