from __future__ import annotations

from decimal import Decimal, InvalidOperation
from pathlib import Path

from fastapi import APIRouter, Depends, Form, HTTPException, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session

from quartermaster import month_service, service
from quartermaster.db import get_session
from quartermaster.models import SECTION_LABELS, Entry, Section

TEMPLATES_DIR = Path(__file__).parent / "templates"
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))

router = APIRouter()


def _parse_amount(raw: str) -> Decimal:
    try:
        amount = Decimal(raw.strip())
    except (InvalidOperation, AttributeError) as exc:
        raise HTTPException(status_code=400, detail="amount must be numeric") from exc
    if amount < 0:
        raise HTTPException(status_code=400, detail="amount must be non-negative")
    return amount.quantize(Decimal("0.01"))


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


def _render_section(
    request: Request,
    db: Session,
    section: Section,
    editing_id: int | None = None,
) -> HTMLResponse:
    view = _section_view(db, section)
    return templates.TemplateResponse(
        request,
        "partials/section.html",
        {"section": view, "editing_id": editing_id},
    )


def _render_target(request: Request, db: Session) -> HTMLResponse:
    target = service.get_debt_target(db)
    debt_minimums = service.list_entries(db, Section.debt_minimum)
    return templates.TemplateResponse(
        request,
        "partials/target_card.html",
        {"target": target, "debt_minimums": debt_minimums},
    )


def _render_zero(request: Request, db: Session) -> HTMLResponse:
    zero = service.budget_zero(db)
    return templates.TemplateResponse(
        request,
        "partials/budget_zero.html",
        {"zero": zero, "tone": service.zero_tone(zero)},
    )


def _render_group_totals(request: Request, db: Session) -> HTMLResponse:
    return templates.TemplateResponse(
        request,
        "partials/budget_group_totals.html",
        {"groups": service.budget_group_views(db)},
    )


def _append_oob(base: HTMLResponse, *extras: HTMLResponse) -> HTMLResponse:
    body = base.body.decode() + "".join(e.body.decode() for e in extras)
    return HTMLResponse(body)


@router.get("/", response_class=HTMLResponse)
def index(request: Request, db: Session = Depends(get_session)) -> HTMLResponse:
    groups = service.budget_group_views(db)
    target = service.get_debt_target(db)
    debt_minimums = service.list_entries(db, Section.debt_minimum)
    current_ym = month_service.current_year_month()
    zero = service.budget_zero(db)
    return templates.TemplateResponse(
        request,
        "index.html",
        {
            "groups": groups,
            "target": target,
            "debt_minimums": debt_minimums,
            "current_year_month": current_ym,
            "all_months": month_service.list_months(db),
            "zero": zero,
            "tone": service.zero_tone(zero),
            "editing_id": None,
        },
    )


@router.post("/sections/{section}/entries", response_class=HTMLResponse)
def create_entry(
    section: Section,
    request: Request,
    name: str = Form(...),
    amount: str = Form(...),
    notes: str | None = Form(None),
    db: Session = Depends(get_session),
) -> HTMLResponse:
    clean_name = name.strip()
    if not clean_name:
        raise HTTPException(status_code=400, detail="name is required")
    parsed = _parse_amount(amount)
    service.add_entry(db, section, clean_name, parsed, notes=notes)
    response = _render_section(request, db, section)
    extras: list[HTMLResponse] = [
        _render_zero(request, db),
        _render_group_totals(request, db),
    ]
    if section == Section.debt_minimum:
        extras.append(_render_target(request, db))
    return _append_oob(response, *extras)


@router.delete("/entries/{entry_id}", response_class=HTMLResponse)
def remove_entry(
    entry_id: int,
    request: Request,
    db: Session = Depends(get_session),
) -> HTMLResponse:
    entry = service.delete_entry(db, entry_id)
    if entry is None:
        raise HTTPException(status_code=404, detail="entry not found")
    response = _render_section(request, db, entry.section)
    extras: list[HTMLResponse] = [
        _render_zero(request, db),
        _render_group_totals(request, db),
    ]
    if entry.section == Section.debt_minimum:
        extras.append(_render_target(request, db))
    return _append_oob(response, *extras)


@router.get("/entries/{entry_id}/edit", response_class=HTMLResponse)
def edit_entry(
    entry_id: int,
    request: Request,
    db: Session = Depends(get_session),
) -> HTMLResponse:
    entry = db.get(Entry, entry_id)
    if entry is None:
        raise HTTPException(status_code=404, detail="entry not found")
    return _render_section(request, db, entry.section, editing_id=entry.id)


@router.post("/entries/{entry_id}", response_class=HTMLResponse)
def save_entry(
    entry_id: int,
    request: Request,
    name: str = Form(...),
    amount: str = Form(...),
    notes: str | None = Form(None),
    db: Session = Depends(get_session),
) -> HTMLResponse:
    clean_name = name.strip()
    if not clean_name:
        raise HTTPException(status_code=400, detail="name is required")
    parsed = _parse_amount(amount)
    updated = service.update_entry(
        db, entry_id, name=clean_name, amount=parsed, notes=notes
    )
    if updated is None:
        raise HTTPException(status_code=404, detail="entry not found")
    response = _render_section(request, db, updated.section)
    extras: list[HTMLResponse] = [
        _render_zero(request, db),
        _render_group_totals(request, db),
    ]
    if updated.section == Section.debt_minimum:
        extras.append(_render_target(request, db))
    return _append_oob(response, *extras)


@router.get("/sections/{section}", response_class=HTMLResponse)
def get_section(
    section: Section,
    request: Request,
    db: Session = Depends(get_session),
) -> HTMLResponse:
    return _render_section(request, db, section)


@router.post("/debt-target", response_class=HTMLResponse)
def update_debt_target(
    request: Request,
    debt_minimum_id: str = Form(""),
    db: Session = Depends(get_session),
) -> HTMLResponse:
    raw = debt_minimum_id.strip()
    target_id: int | None
    if raw == "":
        target_id = None
    else:
        try:
            target_id = int(raw)
        except ValueError as exc:
            raise HTTPException(
                status_code=400, detail="debt_minimum_id must be an integer"
            ) from exc
    try:
        service.set_debt_target(db, target_id)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    return _render_target(request, db)
