import asyncio
import json
from pathlib import Path
from typing import Any
from pydantic import GetJsonSchemaHandler
from pydantic_core import core_schema
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict
from src.utils.retention import RetentionOption
from .db import db_context
from .services.llm_model import LlmModelService
from .common import DATA_DIR


class JsonFileSettingsSource(PydanticBaseSettingsSource):
    def __init__(self, settings_cls: type[BaseSettings], config_path: Path):
        super().__init__(settings_cls)
        self.config_path = config_path

    def get_field_value(self, field, field_name):
        return None, field_name, False

    def __call__(self) -> dict[str, Any]:
        if not self.config_path.exists():
            return {}
        try:
            return json.loads(self.config_path.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            return {}

class JsonSettings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=None,
        env_ignore_empty=True,
        extra="ignore",
    )
    _has_registered_atexit = False

    @classmethod
    def settings_path(cls):
        return DATA_DIR / "settings.json"

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings,
        *args, **kwargs,
    ):
        return (
            init_settings,
            JsonFileSettingsSource(settings_cls, cls.settings_path()),
        )

    @classmethod
    def __get_pydantic_json_schema__(
        cls, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
    ) -> dict[str, Any]:
        json_schema = handler(core_schema) # schema generated by default
        # force to set all fields to be required
        if "properties" in json_schema:
            json_schema["required"] = list(json_schema["properties"].keys())
        return json_schema

    def save(self):
        data = self.model_dump(mode="json")
        self.settings_path().write_text(
            json.dumps(data, indent=2, ensure_ascii=False),
            encoding="utf-8",
        )


class AppSettings(JsonSettings):
    reply_language: str = "zh_CN"
    flash_model: int | None = None

    smart_approve: bool = True
    smart_approve_threshold: int = 50 # 0 ~ 100
    smart_approve_timeout: int = 20

    remote_access: bool = False
    remote_access_port: int = 12586

    task_retention_days: RetentionOption = "disabled"
    schedule_run_record_retention_days: RetentionOption = 30

    async def validate_self(self):
        if self.flash_model is not None:
            async with db_context() as db_session:
                service = LlmModelService(db_session)
                try:
                    await service.get_model_by_id(self.flash_model)
                except Exception:
                    self.flash_model = None

class AppSettingManager:
    def __init__(self):
        self._settings: AppSettings | None = None

    async def initialize(self) -> AppSettings:
        settings = AppSettings()
        await settings.validate_self()
        self._settings = settings
        return self._settings

    @property
    def settings(self) -> AppSettings:
        if self._settings is None:
            raise ValueError("AppSettingManager not initialized")
        return self._settings.model_copy()

    async def update(self, new_settings: AppSettings):
        await new_settings.validate_self()
        self._settings = new_settings

    async def persist(self):
        if self._settings is None:
            raise ValueError("AppSettingManager not initialized")
        await asyncio.to_thread(self._settings.save)

__instance: AppSettingManager | None = None

def use_app_setting_manager() -> AppSettingManager:
    global __instance
    if __instance is None:
        __instance = AppSettingManager()
    return __instance
