from __future__ import annotations

import uuid
from datetime import datetime
from decimal import Decimal
from typing import Any

from sqlalchemy import DateTime, ForeignKey, Index, Numeric, String, Text, UniqueConstraint, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON

from app.db.base_class import Base


class BudgetAllocation(Base):
    __tablename__ = "budget_allocations"
    __table_args__ = (
        UniqueConstraint(
            "fiscal_year",
            "period_key",
            "department_id",
            "cost_center",
            "project_code",
            "subject_code",
            name="uq_budget_allocation_dimension",
        ),
        Index("ix_budget_allocations_dimension", "fiscal_year", "period_key", "subject_code"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    budget_no: Mapped[str] = mapped_column(String(50), unique=True, index=True)
    fiscal_year: Mapped[int] = mapped_column(index=True)
    period_type: Mapped[str] = mapped_column(String(20), default="quarter", index=True)
    period_key: Mapped[str] = mapped_column(String(30), index=True)
    department_id: Mapped[str | None] = mapped_column(
        ForeignKey("organization_units.id"), nullable=True, index=True
    )
    department_name: Mapped[str] = mapped_column(String(100), index=True)
    cost_center: Mapped[str | None] = mapped_column(String(50), nullable=True, index=True)
    project_code: Mapped[str | None] = mapped_column(String(50), nullable=True, index=True)
    subject_code: Mapped[str] = mapped_column(String(50), index=True)
    subject_name: Mapped[str] = mapped_column(String(100))
    original_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=Decimal("0.00"))
    adjusted_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=Decimal("0.00"))
    status: Mapped[str] = mapped_column(String(30), default="active", index=True)
    warning_threshold: Mapped[Decimal] = mapped_column(Numeric(5, 2), default=Decimal("80.00"))
    control_action: Mapped[str] = mapped_column(String(30), default="block")
    description: Mapped[str | None] = mapped_column(Text(), nullable=True)
    created_by: Mapped[str | None] = mapped_column(String(100), nullable=True)
    updated_by: Mapped[str | None] = mapped_column(String(100), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
    )

    transactions = relationship("BudgetTransaction", back_populates="allocation")
    reservations = relationship("BudgetReservation", back_populates="allocation")


class BudgetReservation(Base):
    __tablename__ = "budget_reservations"
    __table_args__ = (
        Index("ix_budget_reservations_source", "source_type", "source_id"),
        Index("ix_budget_reservations_status", "allocation_id", "source_status"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    reservation_no: Mapped[str] = mapped_column(String(50), unique=True, index=True)
    allocation_id: Mapped[str] = mapped_column(ForeignKey("budget_allocations.id"), index=True)
    source_type: Mapped[str] = mapped_column(String(40), index=True)
    source_id: Mapped[str] = mapped_column(String(64), index=True)
    source_no: Mapped[str] = mapped_column(String(80), index=True)
    source_status: Mapped[str] = mapped_column(String(30), default="active", index=True)
    amount: Mapped[Decimal] = mapped_column(Numeric(14, 2))
    consumed_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=Decimal("0.00"))
    released_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=Decimal("0.00"))
    context_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    released_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    consumed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
    )

    allocation = relationship("BudgetAllocation", back_populates="reservations")
    transactions = relationship("BudgetTransaction", back_populates="reservation")


class BudgetTransaction(Base):
    __tablename__ = "budget_transactions"
    __table_args__ = (
        Index("ix_budget_transactions_allocation_created", "allocation_id", "created_at"),
        Index("ix_budget_transactions_source", "source_type", "source_id"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    transaction_no: Mapped[str] = mapped_column(String(50), unique=True, index=True)
    allocation_id: Mapped[str] = mapped_column(ForeignKey("budget_allocations.id"), index=True)
    reservation_id: Mapped[str | None] = mapped_column(
        ForeignKey("budget_reservations.id"), nullable=True, index=True
    )
    source_type: Mapped[str] = mapped_column(String(40), index=True)
    source_id: Mapped[str] = mapped_column(String(64), index=True)
    source_no: Mapped[str] = mapped_column(String(80), index=True)
    transaction_type: Mapped[str] = mapped_column(String(30), index=True)
    amount: Mapped[Decimal] = mapped_column(Numeric(14, 2))
    before_available_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2))
    after_available_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2))
    operator: Mapped[str | None] = mapped_column(String(100), nullable=True)
    reason: Mapped[str | None] = mapped_column(Text(), nullable=True)
    context_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    allocation = relationship("BudgetAllocation", back_populates="transactions")
    reservation = relationship("BudgetReservation", back_populates="transactions")
