import json
import traceback
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, Dict, Literal

from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, field_validator
from rich.console import Console

from utils.tools import CONFIGS


class ToolsConfig(BaseModel):
    apktool_jar_url: str
    apktool_wrapper_url: str

    @field_validator("apktool_jar_url", "apktool_wrapper_url")
    @classmethod
    def validate_url(cls, v: str) -> str:
        if not v.startswith(("http://", "https://")):
            raise ValueError("URL должен начинаться с http:// или https://")
        return v


class SigningConfig(BaseModel):
    keystore: Path = Field(default=Path("keystore.jks"))
    keystore_pass_file: Path = Field(default=Path("keystore.pass"))
    v1_signing: bool = False
    v2_signing: bool = True
    v3_signing: bool = True


class BuildConfig(BaseModel):
    verbose: bool = False
    force: bool = False
    clean_after_build: bool = True


class Config(BaseModel):
    tools: ToolsConfig
    signing: SigningConfig = Field(default_factory=SigningConfig)
    build: BuildConfig = Field(default_factory=BuildConfig)
    base: Dict[str, Any] = Field(default_factory=dict)


def load_config(console: Console) -> Config:
    """Загружает и валидирует конфигурацию"""
    config_path = Path("config.json")

    if not config_path.exists():
        console.print("[red]Файл config.json не найден")
        raise typer.Exit(1)

    try:
        return Config.model_validate_json(config_path.read_text())
    except ValidationError as e:
        console.print(f"[red]Ошибка валидации config.json:\n{e}")
        raise typer.Exit(1)


class PatchTemplate(BaseModel, ABC):
    model_config = ConfigDict(arbitrary_types_allowed=True, validate_default=True)

    enabled: bool = Field(default=True, description="Включить или отключить патч")
    priority: int = Field(default=0, description="Приоритет применения патча")

    _name: str = PrivateAttr()
    _applied: bool = PrivateAttr(default=False)
    _console: Console | None = PrivateAttr(default=None)

    def __init__(self, name: str, console: Console, **data):
        loaded_data = self._load_config_static(name, console)

        merged_data = {**loaded_data, **data}

        valid_fields = set(self.model_fields.keys())
        filtered_data = {k: v for k, v in merged_data.items() if k in valid_fields}

        super().__init__(**filtered_data)
        self._name = name
        self._console = console
        self._applied = False

    @staticmethod
    def _load_config_static(name: str, console: Console | None) -> Dict[str, Any]:
        """Загружает конфигурацию из файла (статический метод)"""
        config_path = CONFIGS / f"{name}.json"
        try:
            if config_path.exists():
                return json.loads(config_path.read_text())
        except Exception as e:
            if console:
                console.print(
                    f"[red]Ошибка при загрузке конфигурации патча {name}: {e}"
                )
                console.print(f"[yellow]Используются значения по умолчанию")
        return {}

    def save_config(self) -> None:
        """Сохраняет конфигурацию в файл"""
        config_path = CONFIGS / f"{self._name}.json"
        config_path.parent.mkdir(parents=True, exist_ok=True)
        config_path.write_text(self.model_dump_json(indent=2))

    @property
    def name(self) -> str:
        return self._name

    @property
    def applied(self) -> bool:
        return self._applied

    @applied.setter
    def applied(self, value: bool) -> None:
        self._applied = value

    @property
    def console(self) -> Console | None:
        return self._console

    @abstractmethod
    def apply(self, base: Dict[str, Any]) -> Any:
        raise NotImplementedError(
            "Попытка применения шаблона патча, а не его реализации"
        )

    def safe_apply(self, base: Dict[str, Any]) -> bool:
        """Безопасно применяет патч с обработкой ошибок"""
        try:
            self._applied = self.apply(base)
            return self._applied
        except Exception as e:
            if self._console:
                self._console.print(f"[red]Ошибка в патче {self._name}: {e}")
                if base.get("verbose"):
                    self._console.print_exception()
            return False
