import logging
import sqlite3
from datetime import date, datetime
from typing import Literal

from pydantic import BaseModel, Field

from lythonic import utc_now
from lythonic.state import DbModel, Schema
from lythonic.types import JsonBase

logger = logging.getLogger(__name__)


AccountType = Literal["cash", "credit_card"]
EventType = Literal["deposit", "payment", "set_balance"]
AmountType = Literal["fixed", "variable"]
Frequency = Literal["weekly", "monthly", "quarterly", "annually"]


class Organization(DbModel["Organization"]):
    org_id: int = Field(default=-1, description="(PK) Unique identifier for the organization")
    name: str = Field(description="Name of the organization")
    created_at: datetime = Field(default_factory=utc_now)
    is_hidden: bool = Field(default=False)


class Account(DbModel["Account"]):
    acc_id: int = Field(default=-1, description="(PK) Unique identifier for the account")
    org_id: int = Field(description="(FK:Organization.org_id) Reference to the organization")
    name: str = Field(description="Name of the account")
    account_type: AccountType = Field(description="Type of account")
    account_kind: str | None = Field(
        default=None,
        description="Account kind pointing to reusable implementation of account logic",
    )
    start_date: date = Field(description="Start date of the account")
    end_date: date | None = Field(default=None, description="End date of the account")
    linked_cash_acc_id: int | None = Field(
        default=None, description="(FK:Account.acc_id) Reference to linked cash account"
    )
    scheduled_activity_id: int | None = Field(
        default=None, description="(FK:ScheduledEvent.sch_id) Reference to scheduled event"
    )
    created_at: datetime = Field(default_factory=utc_now)


class ScheduledEvent(DbModel["ScheduledEvent"]):
    sch_id: int = Field(default=-1, description="(PK) Unique identifier for the scheduled event")
    acc_id: int = Field(description="(FK:Account.acc_id) Reference to the account")
    description_template: str = Field(description="Description template for the event")
    event_type: EventType = Field(description="Type of event")
    amount_type: AmountType = Field(description="Type of amount")
    amount: float | None = Field(default=None, description="Fixed amount or NULL for variable")
    frequency: Frequency = Field(description="Frequency of the event")
    day_in_the_cycle: int = Field(
        description="Day of the cycle (day of week 0-6/month 1-31/year 0-365)"
    )
    reminder_days: int | None = Field(default=None, description="Days before due to remind user")

    # versioning fields
    root_version_id: int | None = Field(
        default=None,
        description="(FK:ScheduledEvent.sch_id) Reference to the root version",
    )
    is_active: bool = Field(default=True, description="Whether the event is active")
    start_date: date = Field(description="Start date of the event")
    end_date: date | None = Field(default=None, description="End date of the event")
    modified_at: datetime = Field(default_factory=utc_now)

    def save_versioned(self, conn: sqlite3.Connection, as_of: date | None = None) -> None:
        if as_of is None:
            as_of = date.today()
        assert self.end_date is None, (
            f"use enddate_record() explicitly if you want logicali remove this record: {self}"
        )
        assert self.root_version_id is None and self.start_date <= as_of and self.is_active, (
            f"sainity check failed: {self}"
        )
        if self.sch_id == -1:  # new record
            self.root_version_id = None
            self.start_date = as_of
            self.end_date = None
        else:
            rr = self.select(conn, sch_id=self.sch_id)
            assert len(rr) == 1
            # save previous version
            prev = rr[0]
            assert prev.end_date is None, f"cannot modify enddated record: {prev}"
            assert prev.start_date <= as_of, f"sainity check failed: {prev}"
            if prev.start_date != as_of:  # save previous version
                prev.root_version_id = prev.sch_id
                prev.sch_id = -1
                prev.is_active = False
                prev.end_date = as_of
                prev.save(conn)
        self.start_date = as_of
        self.is_active = True
        self.root_version_id = None
        self.modified_at = utc_now()
        self.save(conn)

    def enddate_record(self, conn: sqlite3.Connection, as_of: date) -> None:
        assert self.end_date is None, f"already ended: {self}"
        assert self.start_date <= as_of and self.is_active, f"sainity check failed: {self}"
        self.end_date = as_of
        self.is_active = False
        self.root_version_id = None
        self.modified_at = utc_now()
        self.save(conn)


class CashEvent(DbModel["CashEvent"]):
    cash_id: int = Field(default=-1, description="(PK) Unique identifier for the cash event")
    cash_acc_id: int = Field(description="(FK:Account.acc_id) Reference to cash account")
    cc_acc_id: int | None = Field(
        default=None, description="(FK:Account.acc_id) Reference to credit card account"
    )
    sch_id: int | None = Field(
        default=None, description="(FK:ScheduledEvent.sch_id) Reference to scheduled event"
    )
    event_type: EventType = Field(description="Type of event")
    event_date: date = Field(description="Date of the event")
    amount: float = Field(description="Amount of the event")
    description: str | None = Field(default=None, description="Description of the event")
    modified_at: datetime = Field(default_factory=utc_now)


class CashEventNotification(DbModel["CashEventNotification"]):
    """
    Represents a notification that a cash event needs to be created but requires user input.
    """

    note_id: int = Field(default=-1, description="(PK) Unique identifier for the notification")
    sch_id: int = Field(description="(FK:ScheduledEvent.sch_id) Reference to scheduled event")
    note_date: date = Field(description="Date of the notification")
    event_date: date = Field(description="Date of the event")


class FlowItem(BaseModel):
    EventType: EventType
    Amount: float
    Description: str | None
    cash_id: int | None = Field(
        default=None,
        description="Indicate if this event is already materialized as CashEvent or it is future event",
    )
    note_id: int | None = Field(
        default=None, description="Indicate if this event has associated CashEventNotification"
    )
    balance: float = Field(description="Balance after this event")


class FlowProjection(JsonBase):
    as_of: date = Field(description="Date of the projection typically today")
    events: list[FlowItem] = Field(description="List of events materialized and projected")
    alert: bool = Field(
        description="Indicate if if balance is negative or close to zero at any point in the projected future"
    )


class CashAccountProjection(DbModel["CashAccountProjection"]):
    """
    Cash account projection for a given date and extending for month in future. All scheduled events
    that triggered prior to this date are materilaized as CashEvent and CashEventNotification to calculate the projection.
    """

    cash_acc_id: int = Field(description="(FK:Account.acc_id) Reference to cash account")
    projection: FlowProjection | None = Field(
        default=None,
        description="Projection for next month starting from last known balance (set_balance event)",
    )
    as_of: date = Field(description="Date of projection typically today")
    modified_at: datetime = Field(default_factory=utc_now)


SCHEMA = Schema(tables=[Organization, Account, ScheduledEvent, CashEvent, CashEventNotification, CashAccountProjection])
