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

StrId = Annotated[str, BeforeValidator(str)]


# CalculationSettings schemas
class CalculationsettingsBase(BaseModel):
    settings_id: str = Field(..., max_length=100)
    default_loan_amount: Decimal = Field(..., max_digits=15, decimal_places=2, ge=Decimal("100"), le=Decimal("100000000"))
    default_interest_rate: Decimal = Field(..., max_digits=5, decimal_places=2, ge=Decimal("0.01"), le=Decimal("99.99"))
    default_loan_term: int = Field(..., ge=1, le=480)
    currency_symbol: str = Field(..., max_length=10)
    decimal_precision: int = Field(..., ge=0)
    date_format: str = Field(..., max_length=50)

    @field_validator("settings_id")
    @classmethod
    def validate_settings_id(cls, v):
        if v is None:
            return v
        return v.strip()

    @field_validator("currency_symbol", "date_format")
    @classmethod
    def validate_string_fields(cls, v):
        if v is None:
            return v
        return v.strip()


class CalculationsettingsCreate(CalculationsettingsBase):
    pass


class CalculationsettingsUpdate(BaseModel):
    settings_id: Optional[str] = Field(None, max_length=100)
    default_loan_amount: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=Decimal("100"), le=Decimal("100000000"))
    default_interest_rate: Optional[Decimal] = Field(None, max_digits=5, decimal_places=2, ge=Decimal("0.01"), le=Decimal("99.99"))
    default_loan_term: Optional[int] = Field(None, ge=1, le=480)
    currency_symbol: Optional[str] = Field(None, max_length=10)
    decimal_precision: Optional[int] = Field(None, ge=0)
    date_format: Optional[str] = Field(None, max_length=50)

    @field_validator("settings_id", "currency_symbol", "date_format")
    @classmethod
    def validate_string_fields(cls, v):
        if v is None:
            return v
        return v.strip()


class CalculationsettingsResponse(CalculationsettingsBase):
    id: StrId
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


# LoanCalculation schemas
class LoancalculationBase(BaseModel):
    calculation_id: str = Field(..., max_length=100)
    settings_id: Optional[StrId] = None
    principal_amount: Decimal = Field(..., max_digits=15, decimal_places=2, ge=Decimal("100"), le=Decimal("100000000"))
    annual_interest_rate: Decimal = Field(..., max_digits=5, decimal_places=2, ge=Decimal("0.01"), le=Decimal("99.99"))
    loan_term_months: int = Field(..., ge=1, le=480)
    monthly_payment: Decimal = Field(..., max_digits=15, decimal_places=2)
    total_interest: Decimal = Field(..., max_digits=15, decimal_places=2)
    total_amount_paid: Decimal = Field(..., max_digits=15, decimal_places=2)
    calculation_timestamp: datetime

    @field_validator("calculation_id")
    @classmethod
    def validate_calculation_id(cls, v):
        if v is None:
            return v
        return v.strip()

    @field_validator("principal_amount", "monthly_payment", "total_interest", "total_amount_paid")
    @classmethod
    def validate_positive_amounts(cls, v):
        if v is not None and v < 0:
            raise ValueError("Amount must be positive")
        return v

    @field_validator("annual_interest_rate")
    @classmethod
    def validate_interest_rate(cls, v):
        if v is not None and v < 0:
            raise ValueError("Interest rate must be positive")
        return v

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


class LoancalculationCreate(LoancalculationBase):
    pass


class LoancalculationUpdate(BaseModel):
    calculation_id: Optional[str] = Field(None, max_length=100)
    settings_id: Optional[StrId] = None
    principal_amount: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2, ge=Decimal("100"), le=Decimal("100000000"))
    annual_interest_rate: Optional[Decimal] = Field(None, max_digits=5, decimal_places=2, ge=Decimal("0.01"), le=Decimal("99.99"))
    loan_term_months: Optional[int] = Field(None, ge=1, le=480)
    monthly_payment: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    total_interest: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    total_amount_paid: Optional[Decimal] = Field(None, max_digits=15, decimal_places=2)
    calculation_timestamp: Optional[datetime] = None

    @field_validator("calculation_id")
    @classmethod
    def validate_calculation_id(cls, v):
        if v is None:
            return v
        return v.strip()

    @field_validator("principal_amount", "monthly_payment", "total_interest", "total_amount_paid")
    @classmethod
    def validate_positive_amounts(cls, v):
        if v is not None and v < 0:
            raise ValueError("Amount must be positive")
        return v


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

    model_config = ConfigDict(from_attributes=True)


# AmortizationEntry schemas
class AmortizationentryBase(BaseModel):
    entry_id: str = Field(..., max_length=100)
    calculation_id: StrId
    payment_number: int = Field(..., ge=1)
    payment_date: date
    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)

    @field_validator("entry_id")
    @classmethod
    def validate_entry_id(cls, v):
        if v is None:
            return v
        return v.strip()

    @field_validator("payment_amount", "principal_portion", "interest_portion", "remaining_balance")
    @classmethod
    def validate_non_negative_amounts(cls, v):
        if v is not None and v < 0:
            raise ValueError("Amount must be non-negative")
        return v


class AmortizationentryCreate(AmortizationentryBase):
    pass


class AmortizationentryUpdate(BaseModel):
    entry_id: Optional[str] = Field(None, max_length=100)
    calculation_id: Optional[StrId] = None
    payment_number: Optional[int] = Field(None, ge=1)
    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)

    @field_validator("entry_id")
    @classmethod
    def validate_entry_id(cls, v):
        if v is None:
            return v
        return v.strip()

    @field_validator("payment_amount", "principal_portion", "interest_portion", "remaining_balance")
    @classmethod
    def validate_non_negative_amounts(cls, v):
        if v is not None and v < 0:
            raise ValueError("Amount must be non-negative")
        return v


class AmortizationentryResponse(AmortizationentryBase):
    id: StrId
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


# Detail endpoint response schemas
class AmortizationentrySummary(BaseModel):
    payment_number: int
    payment_date: date
    payment_amount: Decimal
    principal_portion: Decimal
    interest_portion: Decimal
    remaining_balance: Decimal

    model_config = ConfigDict(from_attributes=True)


class CalculationsettingsSummary(BaseModel):
    currency_symbol: str
    decimal_precision: int
    date_format: str

    model_config = ConfigDict(from_attributes=True)


class LoancalculationDetailResponse(BaseModel):
    id: StrId
    calculation_id: str
    settings_id: Optional[StrId]
    principal_amount: Decimal
    annual_interest_rate: Decimal
    loan_term_months: int
    monthly_payment: Decimal
    total_interest: Decimal
    total_amount_paid: Decimal
    calculation_timestamp: datetime
    created_at: datetime
    updated_at: datetime
    settings: Optional[CalculationsettingsSummary]
    amortization_entries: List[AmortizationentrySummary]

    model_config = ConfigDict(from_attributes=True)


# Paginated response wrapper
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)