from __future__ import annotations

import json
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any


PROJECT_ROOT = Path(__file__).resolve().parents[2]
DEFAULT_CONFIG_PATH = PROJECT_ROOT / "config" / "app_settings.json"
DEFAULT_DATABASE_URL = "mysql+pymysql://root:12345678@localhost:3306/financial_system?charset=utf8mb4"
DEFAULT_CORS_ALLOWED_ORIGINS = (
    "http://127.0.0.1:5173",
    "http://localhost:5173",
    "http://127.0.0.1:5174",
    "http://localhost:5174",
)


@dataclass(frozen=True)
class AppSettings:
    """运行时配置，统一来自 config/app_settings.json。"""

    project_root: Path = PROJECT_ROOT
    config_path: Path = DEFAULT_CONFIG_PATH
    server_host: str = "0.0.0.0"
    server_port: int = 8000
    server_fallback_port: int = 8001
    log_dir: Path = PROJECT_ROOT / "logs"
    log_level: str = "INFO"
    log_info_file: str = "info.log"
    log_error_file: str = "error.log"
    log_max_bytes: int = 10 * 1024 * 1024
    log_backup_count: int = 10
    log_console_enabled: bool = True
    upload_dir: Path = PROJECT_ROOT / "uploads"
    output_dir: Path = PROJECT_ROOT / "outputs"
    avatar_dir: Path = PROJECT_ROOT / "static" / "uploads" / "avatars"
    database_url: str = DEFAULT_DATABASE_URL
    dingtalk_app_key: str = ""
    dingtalk_app_secret: str = ""
    dingtalk_base_url: str = "https://oapi.dingtalk.com"
    dingtalk_timezone: str = "Asia/Shanghai"
    auth_secret_key: str = "financial-system-dev-secret"
    access_token_expire_minutes: int = 480
    cors_allowed_origins: tuple[str, ...] = DEFAULT_CORS_ALLOWED_ORIGINS
    bootstrap_superuser_username: str = "admin"
    bootstrap_superuser_password: str = "Admin@123456"
    bootstrap_superuser_display_name: str = "超级管理员"


def get_settings() -> AppSettings:
    raw = _load_settings_file()
    server = _section(raw, "server")
    logging_config = _section(raw, "logging")
    storage = _section(raw, "storage")
    database = _section(raw, "database")
    dingtalk = _section(raw, "dingtalk")
    auth = _section(raw, "auth")
    bootstrap_superuser = _section(raw, "bootstrap_superuser")
    cors = _section(raw, "cors")

    return AppSettings(
        config_path=_resolve_config_path(),
        server_host=str(server.get("host", "0.0.0.0")),
        server_port=int(server.get("port", 8000)),
        server_fallback_port=int(server.get("fallback_port", 8001)),
        log_dir=_project_path(logging_config.get("dir", "logs")),
        log_level=str(logging_config.get("level", "INFO")).upper(),
        log_info_file=str(logging_config.get("info_file", "info.log")),
        log_error_file=str(logging_config.get("error_file", "error.log")),
        log_max_bytes=int(logging_config.get("max_bytes", 10 * 1024 * 1024)),
        log_backup_count=int(logging_config.get("backup_count", 10)),
        log_console_enabled=_bool(logging_config.get("console_enabled", True)),
        upload_dir=_project_path(storage.get("upload_dir", "uploads")),
        output_dir=_project_path(storage.get("output_dir", "outputs")),
        avatar_dir=_project_path(storage.get("avatar_dir", "static/uploads/avatars")),
        database_url=_env_string("DATABASE_URL", database.get("url", DEFAULT_DATABASE_URL)),
        dingtalk_app_key=str(dingtalk.get("app_key", "")).strip(),
        dingtalk_app_secret=str(dingtalk.get("app_secret", "")).strip(),
        dingtalk_base_url=str(dingtalk.get("base_url", "https://oapi.dingtalk.com")).rstrip("/"),
        dingtalk_timezone=str(dingtalk.get("timezone", "Asia/Shanghai")),
        auth_secret_key=str(auth.get("secret_key", "financial-system-dev-secret")),
        access_token_expire_minutes=int(auth.get("access_token_expire_minutes", 480)),
        cors_allowed_origins=_string_tuple(
            cors.get("allowed_origins", DEFAULT_CORS_ALLOWED_ORIGINS),
            DEFAULT_CORS_ALLOWED_ORIGINS,
        ),
        bootstrap_superuser_username=str(bootstrap_superuser.get("username", "admin")),
        bootstrap_superuser_password=str(bootstrap_superuser.get("password", "Admin@123456")),
        bootstrap_superuser_display_name=str(bootstrap_superuser.get("display_name", "超级管理员")),
    )


def _load_settings_file() -> dict[str, Any]:
    config_path = _resolve_config_path()
    if not config_path.exists():
        if os.getenv("APP_CONFIG_FILE"):
            raise FileNotFoundError(f"配置文件不存在：{config_path}")
        return {}

    raw = json.loads(config_path.read_text(encoding="utf-8"))
    if not isinstance(raw, dict):
        raise ValueError(f"配置文件根节点必须是 JSON 对象：{config_path}")
    return raw


def _resolve_config_path() -> Path:
    configured_path = os.getenv("APP_CONFIG_FILE", "").strip()
    if not configured_path:
        return DEFAULT_CONFIG_PATH

    path = Path(configured_path).expanduser()
    return path if path.is_absolute() else PROJECT_ROOT / path


def _section(raw: dict[str, Any], name: str) -> dict[str, Any]:
    value = raw.get(name, {})
    if not isinstance(value, dict):
        raise ValueError(f"配置项 {name} 必须是 JSON 对象")
    return value


def _project_path(value: object) -> Path:
    path = Path(str(value)).expanduser()
    return path if path.is_absolute() else PROJECT_ROOT / path


def _string_tuple(value: object, default: tuple[str, ...]) -> tuple[str, ...]:
    if value is None:
        return default
    if isinstance(value, str):
        items = value.split(",")
    elif isinstance(value, list | tuple):
        items = value
    else:
        raise ValueError("字符串列表配置必须是数组或逗号分隔字符串")

    normalized = tuple(str(item).strip() for item in items if str(item).strip())
    return normalized or default


def _env_string(name: str, fallback: object) -> str:
    configured_value = os.getenv(name, "").strip()
    return configured_value or str(fallback).strip()


def _bool(value: object) -> bool:
    if isinstance(value, bool):
        return value
    return str(value).strip().lower() in {"1", "true", "yes", "y", "on"}
