from __future__ import annotations

import json
import math
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING

from ccma.domain.models import DEFAULT_OPTIONAL_MEMBER_FIELDS, normalize_optional_member_fields
from ccma.storage.atomic import write_json_atomic

if TYPE_CHECKING:
    from ccma.services.housekeeper import HousekeeperSettings


@dataclass(slots=True)
class AppConfig:
    store_path: str = ""
    gnucash_path: str = ""
    gnucash_last_accounts: dict[str, str] = field(default_factory=dict)
    theme_mode: str = "dark"
    run_housekeeper_on_startup: bool = True
    splash_minimum_seconds: float = 5.0
    birthday_days_before: int = 7
    birthday_days_after: int = 2
    anniversary_days_before: int = 14
    anniversary_days_after: int = 7
    anniversary_intervals: str = "1Y;5Y;10Y;25Y;50Y"
    retroactive_claims: bool = False
    optional_member_fields: tuple[str, ...] = DEFAULT_OPTIONAL_MEMBER_FIELDS
    window_geometry: str = ""
    window_state: str = "normal"
    monitor_bounds: tuple[int, int, int, int] | None = None

    @property
    def path(self) -> Path:
        return config_directory() / "config.json"

    def save(self) -> None:
        write_json_atomic(
            self.path,
            {
                "schema_version": 1,
                "store_path": self.store_path,
                "gnucash_path": self.gnucash_path,
                "gnucash_last_accounts": self.gnucash_last_accounts,
                "theme_mode": self.theme_mode,
                "run_housekeeper_on_startup": self.run_housekeeper_on_startup,
                "splash_minimum_seconds": _non_negative_float(self.splash_minimum_seconds, 5.0),
                "birthday_days_before": self.birthday_days_before,
                "birthday_days_after": self.birthday_days_after,
                "anniversary_days_before": self.anniversary_days_before,
                "anniversary_days_after": self.anniversary_days_after,
                "anniversary_intervals": self.anniversary_intervals,
                "retroactive_claims": self.retroactive_claims,
                "optional_member_fields": list(normalize_optional_member_fields(self.optional_member_fields)),
                "window_geometry": self.window_geometry,
                "window_state": self.window_state,
                "monitor_bounds": list(self.monitor_bounds) if self.monitor_bounds else None,
            },
        )

    def housekeeper_settings(self) -> HousekeeperSettings:
        from ccma.services.housekeeper import HousekeeperSettings
        from ccma.services.intervals import IntervalValidationError

        try:
            return HousekeeperSettings.from_values(
                birthday_days_before=self.birthday_days_before,
                birthday_days_after=self.birthday_days_after,
                anniversary_days_before=self.anniversary_days_before,
                anniversary_days_after=self.anniversary_days_after,
                anniversary_intervals=self.anniversary_intervals,
                retroactive_claims=self.retroactive_claims,
                optional_member_fields=self.optional_member_fields,
            )
        except IntervalValidationError:
            return HousekeeperSettings()


def config_directory() -> Path:
    override = os.environ.get("CCMA_CONFIG_DIR")
    if override:
        return Path(override).expanduser()
    if os.name == "nt":
        return Path(os.environ.get("APPDATA", Path.home())) / "CCMA"
    return Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "ccma"


def load_config() -> AppConfig:
    path = config_directory() / "config.json"
    store_override = os.environ.get("CCMA_STORE", "")
    if not path.exists():
        return AppConfig(store_path=store_override)
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        monitor_raw = data.get("monitor_bounds")
        monitor_bounds = None
        if isinstance(monitor_raw, list) and len(monitor_raw) == 4:
            monitor_bounds = tuple(int(value) for value in monitor_raw)
        last_accounts_raw = data.get("gnucash_last_accounts")
        gnucash_last_accounts = (
            {str(key): str(value) for key, value in last_accounts_raw.items()}
            if isinstance(last_accounts_raw, dict)
            else {}
        )
        return AppConfig(
            store_path=store_override or str(data.get("store_path", "")),
            gnucash_path=str(data.get("gnucash_path", "")),
            gnucash_last_accounts=gnucash_last_accounts,
            theme_mode=str(data.get("theme_mode", "dark")),
            run_housekeeper_on_startup=bool(data.get("run_housekeeper_on_startup", True)),
            splash_minimum_seconds=_non_negative_float(data.get("splash_minimum_seconds", 5.0), 5.0),
            birthday_days_before=int(data.get("birthday_days_before", 7)),
            birthday_days_after=int(data.get("birthday_days_after", 2)),
            anniversary_days_before=int(data.get("anniversary_days_before", 14)),
            anniversary_days_after=int(data.get("anniversary_days_after", 7)),
            anniversary_intervals=str(data.get("anniversary_intervals", "1Y;5Y;10Y;25Y;50Y")),
            retroactive_claims=bool(data.get("retroactive_claims", False)),
            optional_member_fields=normalize_optional_member_fields(
                data.get("optional_member_fields", DEFAULT_OPTIONAL_MEMBER_FIELDS)
            ),
            window_geometry=str(data.get("window_geometry", "")),
            window_state=str(data.get("window_state", "normal")),
            monitor_bounds=monitor_bounds,
        )
    except (OSError, ValueError, TypeError):
        return AppConfig(store_path=store_override)


def _non_negative_float(value: object, default: float) -> float:
    try:
        parsed = float(value)
    except (TypeError, ValueError):
        return default
    return max(0.0, parsed) if math.isfinite(parsed) else default
