from __future__ import annotations

import logging
from dataclasses import dataclass
from decimal import Decimal

from sqlalchemy import select
from sqlalchemy.orm import Session

logger = logging.getLogger("quartermaster.service")

from quartermaster.groups import (
    GROUP_DEFAULT_OPEN,
    GROUP_LABELS,
    Group,
    group_order,
    sections_in_group,
)
from quartermaster.models import SECTION_LABELS, DebtTarget, Entry, Section

_NOTES_SENTINEL = object()


def zero_tone(value: Decimal) -> str:
    if value == 0:
        return "zero"
    return "positive" if value > 0 else "negative"


@dataclass(frozen=True)
class SectionView:
    section: Section
    label: str
    entries: list[Entry]
    total: Decimal


@dataclass(frozen=True)
class BudgetGroupView:
    group: Group
    label: str
    default_open: bool
    sections: list[SectionView]
    total: Decimal


def list_entries(db: Session, section: Section) -> list[Entry]:
    stmt = select(Entry).where(Entry.section == section).order_by(Entry.id)
    return list(db.scalars(stmt))


def section_total(entries: list[Entry]) -> Decimal:
    return sum((e.amount for e in entries), Decimal("0"))


def section_view(db: Session, section: Section) -> SectionView:
    entries = list_entries(db, section)
    return SectionView(
        section=section,
        label=SECTION_LABELS[section],
        entries=entries,
        total=section_total(entries),
    )


def budget_group_views(db: Session) -> list[BudgetGroupView]:
    views: list[BudgetGroupView] = []
    for group in group_order():
        sections = [section_view(db, s) for s in sections_in_group(group)]
        total = sum((sv.total for sv in sections), Decimal("0"))
        views.append(
            BudgetGroupView(
                group=group,
                label=GROUP_LABELS[group],
                default_open=GROUP_DEFAULT_OPEN[group],
                sections=sections,
                total=total,
            )
        )
    return views


def budget_zero(db: Session) -> Decimal:
    stmt = select(Entry)
    total_income = Decimal("0")
    total_non_income = Decimal("0")
    for entry in db.scalars(stmt):
        if entry.section == Section.income:
            total_income += entry.amount
        else:
            total_non_income += entry.amount
    return (total_income - total_non_income).quantize(Decimal("0.01"))


def add_entry(
    db: Session,
    section: Section,
    name: str,
    amount: Decimal,
    notes: str | None = None,
) -> Entry:
    entry = Entry(
        section=section,
        name=name.strip(),
        amount=amount,
        notes=_clean_notes(notes),
    )
    db.add(entry)
    db.commit()
    db.refresh(entry)
    return entry


def _clean_notes(raw: str | None) -> str | None:
    if raw is None:
        return None
    stripped = raw.strip()
    return stripped if stripped else None


def update_entry(
    db: Session,
    entry_id: int,
    *,
    name: str | None = None,
    amount: Decimal | None = None,
    notes: str | None | object = _NOTES_SENTINEL,
) -> Entry | None:
    entry = db.get(Entry, entry_id)
    if entry is None:
        return None
    if name is not None:
        entry.name = name.strip()
    if amount is not None:
        entry.amount = amount
    if notes is not _NOTES_SENTINEL:
        entry.notes = _clean_notes(notes)  # type: ignore[arg-type]
    db.commit()
    db.refresh(entry)
    logger.info(
        "updated template entry",
        extra={"event": "template_entry_updated", "entry_id": entry.id},
    )
    return entry


def delete_entry(db: Session, entry_id: int) -> Entry | None:
    entry = db.get(Entry, entry_id)
    if entry is None:
        return None
    db.delete(entry)
    db.commit()
    return entry


def get_debt_target(db: Session) -> DebtTarget:
    target = db.get(DebtTarget, 1)
    if target is None:
        target = DebtTarget(id=1, debt_minimum_id=None)
        db.add(target)
        db.commit()
        db.refresh(target)
    return target


def set_debt_target(db: Session, debt_minimum_id: int | None) -> DebtTarget:
    target = get_debt_target(db)
    if debt_minimum_id is not None:
        candidate = db.get(Entry, debt_minimum_id)
        if candidate is None or candidate.section != Section.debt_minimum:
            raise ValueError("debt_minimum_id must reference a debt minimum entry")
    target.debt_minimum_id = debt_minimum_id
    db.commit()
    db.refresh(target)
    return target
