from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from pathlib import Path

from smartwallet.config import get_data_path
from smartwallet.model import Category, Transaction

SCHEMA_VERSION = 1
DEFAULT_ROOT_NAME = "Root"


def save_budget(root: Category, path: Path | None = None) -> None:
    target = path or get_data_path()
    target.parent.mkdir(parents=True, exist_ok=True)

    payload = serialize_budget(root)
    target.write_text(json.dumps(payload, indent=2), encoding="utf-8")


def load_budget(path: Path | None = None) -> Category:
    source = path or get_data_path()
    if not source.exists():
        return Category(name=DEFAULT_ROOT_NAME)

    try:
        content = json.loads(source.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        raise ValueError("Invalid JSON in data file.") from exc

    return deserialize_budget(content)


def serialize_budget(root: Category) -> dict[str, Any]:
    return {"schema_version": SCHEMA_VERSION, "data": root.to_dict()}


def deserialize_budget(payload: dict[str, Any]) -> Category:
    if payload.get("schema_version") != SCHEMA_VERSION:
        raise ValueError("Unsupported schema version.")

    data = payload.get("data")
    if not isinstance(data, dict):
        raise ValueError("Missing or invalid data payload.")

    root = _component_from_dict(data)
    if not isinstance(root, Category):
        raise ValueError("Root component must be a category.")

    return root


def _component_from_dict(payload: dict[str, Any]) -> Category | Transaction:
    component_type = payload.get("type")
    name = payload.get("name")
    component_id = payload.get("id")
    created_at = payload.get("created_at")

    if not all(isinstance(value, str) for value in [name, component_id, created_at]):
        raise ValueError("Serialized component is missing required fields.")

    if component_type == "transaction":
        amount = payload.get("amount")
        transaction_date = payload.get("transaction_date")
        if not isinstance(amount, (int, float)) or not isinstance(
            transaction_date, str
        ):
            raise ValueError("Serialized transaction is invalid.")

        return Transaction(
            name=name,
            id=component_id,
            created_at=created_at,
            amount=float(amount),
            transaction_date=transaction_date,
        )

    if component_type == "category":
        children_payload = payload.get("children", [])
        if not isinstance(children_payload, list):
            raise ValueError("Serialized category children are invalid.")

        category = Category(name=name, id=component_id, created_at=created_at)
        for child in children_payload:
            if not isinstance(child, dict):
                raise ValueError("Serialized child component is invalid.")
            category.add(_component_from_dict(child))

        return category

    raise ValueError("Unknown component type in serialized data.")
