"""System settings.

System-wide parameters (closing day, global start date, auto-reminder toggle
and lead days) are persisted in ``system_settings`` with an append-only
``system_setting_changes`` trail. The auto-reminder engine itself lives in
``reminders.py`` (HEL-195); this module only owns the settings store.
"""

from __future__ import annotations

import re
import sqlite3
from datetime import datetime

from .db import utc_now


# Defaults are applied when a key is absent; the value type is always string.
DEFAULT_SETTINGS: dict[str, str] = {
    "closing_day": "5",
    "start_date": "2026-01-01",
    "auto_remind": "1",
    "remind_days": "3",
}

_SETTING_KEYS = frozenset({"closing_day", "start_date", "auto_remind", "remind_days"})

_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")


def _valid_date(value: str) -> bool:
    if not _DATE_RE.match(value):
        return False
    try:
        datetime.strptime(value, "%Y-%m-%d")
    except ValueError:
        return False
    return True


def get_settings(connection: sqlite3.Connection) -> dict[str, str]:
    settings = dict(DEFAULT_SETTINGS)
    rows = connection.execute(
        "SELECT key, value FROM system_settings"
    ).fetchall()
    for row in rows:
        settings[row["key"]] = row["value"]
    return settings


def validate_settings(values: dict[str, object]) -> tuple[dict[str, str], str | None]:
    """Return ``(cleaned, error)``; ``cleaned`` holds only recognized keys."""
    cleaned: dict[str, str] = {}
    if "closing_day" in values:
        raw = str(values["closing_day"]).strip()
        try:
            day = int(raw)
        except ValueError:
            return cleaned, "每月结账日须为 1-28 之间的整数。"
        if day < 1 or day > 28:
            return cleaned, "每月结账日须为 1-28 之间的整数。"
        cleaned["closing_day"] = str(day)
    if "start_date" in values:
        raw = str(values["start_date"]).strip()
        if not _valid_date(raw):
            return cleaned, "全局起算日须为有效日期（YYYY-MM-DD）。"
        cleaned["start_date"] = raw
    if "auto_remind" in values:
        raw = str(values["auto_remind"]).strip()
        if raw not in {"0", "1"}:
            return cleaned, "自动提醒开关须为 0 或 1。"
        cleaned["auto_remind"] = raw
    if "remind_days" in values:
        raw = str(values["remind_days"]).strip()
        try:
            days = int(raw)
        except ValueError:
            return cleaned, "提前提醒天数须为不小于 1 的整数。"
        if days < 1 or days > 30:
            return cleaned, "提前提醒天数须为 1-30 之间的整数。"
        cleaned["remind_days"] = str(days)
    return cleaned, None


def update_settings(
    connection: sqlite3.Connection,
    values: dict[str, object],
    actor: sqlite3.Row,
) -> dict[str, str]:
    cleaned, error = validate_settings(values)
    if error is not None:
        raise ValueError(error)
    if not cleaned:
        raise ValueError("没有需要保存的设置项。")
    current = get_settings(connection)
    with connection:
        for key, new_value in cleaned.items():
            old_value = current.get(key)
            if old_value == new_value:
                continue
            connection.execute(
                """
                INSERT INTO system_settings (key, value, updated_by, updated_at)
                VALUES (?, ?, ?, ?)
                ON CONFLICT(key) DO UPDATE SET
                    value = excluded.value,
                    updated_by = excluded.updated_by,
                    updated_at = excluded.updated_at
                """,
                (key, new_value, actor["id"], utc_now()),
            )
            connection.execute(
                """
                INSERT INTO system_setting_changes (
                    key, before_value, after_value,
                    actor_user_id, actor_username, created_at
                ) VALUES (?, ?, ?, ?, ?, ?)
                """,
                (
                    key,
                    old_value,
                    new_value,
                    actor["id"],
                    actor["username"],
                    utc_now(),
                ),
            )
    return get_settings(connection)


# ----------------------------------------------------------------------
# Reminder pending items
# ----------------------------------------------------------------------
