import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Optional

import yaml
from pydantic import BaseModel, Field, computed_field
from rich.console import Console

from utils.tools import DECOMPILED, MODIFIED, TOOLS, run


class APKMeta(BaseModel):
    """Метаданные APK файла"""

    version_code: int = Field(default=0)
    version_name: str = Field(default="unknown")
    package: str = Field(default="unknown")
    path: Path

    @computed_field
    @property
    def safe_version(self) -> str:
        """Версия, безопасная для использования в именах файлов"""
        return self.version_name.lower().replace(" ", "-").replace(".", "-")

    @computed_field
    @property
    def output_name(self) -> str:
        """Имя выходного файла"""
        return f"Anixarts-v{self.safe_version}.apk"

    @computed_field
    @property
    def aligned_name(self) -> str:
        """Имя выровненного файла"""
        return f"Anixarts-v{self.safe_version}-aligned.apk"

    @computed_field
    @property
    def signed_name(self) -> str:
        """Имя подписанного файла"""
        return f"Anixarts-v{self.safe_version}-mod.apk"


class SigningConfig(BaseModel):
    """Конфигурация подписи APK"""

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


class APKProcessor:
    """Класс для работы с APK файлами"""

    def __init__(self, console: Console, tools_dir: Path = TOOLS):
        self.console = console
        self.tools_dir = tools_dir
        self.apktool_jar = tools_dir / "apktool.jar"

    def decompile(self, apk: Path, output: Path = DECOMPILED) -> None:
        """Декомпилирует APK файл"""
        self.console.print("[yellow]Декомпиляция APK...")
        run(
            self.console,
            [
                "java",
                "-jar",
                str(self.apktool_jar),
                "d",
                "-f",
                "-o",
                str(output),
                str(apk),
            ],
        )
        self.console.print("[green]✔ Декомпиляция завершена")

    def compile(self, source: Path, output: Path) -> None:
        """Компилирует APK из исходников"""
        self.console.print("[yellow]Сборка APK...")
        run(
            self.console,
            [
                "java",
                "-jar",
                str(self.apktool_jar),
                "b",
                str(source),
                "-o",
                str(output),
            ],
        )
        self.console.print("[green]✔ Сборка завершена")

    def align(self, input_apk: Path, output_apk: Path) -> None:
        """Выравнивает APK с помощью zipalign"""
        self.console.print("[yellow]Выравнивание APK...")
        run(
            self.console, ["zipalign", "-f", "-v", "4", str(input_apk), str(output_apk)]
        )
        self.console.print("[green]✔ Выравнивание завершено")

    def sign(
        self,
        input_apk: Path,
        output_apk: Path,
        config: Optional[SigningConfig] = None,
    ) -> None:
        """Подписывает APK"""
        if config is None:
            config = SigningConfig()

        self.console.print("[yellow]Подпись APK...")
        run(
            self.console,
            [
                "apksigner",
                "sign",
                "--v1-signing-enabled",
                str(config.v1_signing).lower(),
                "--v2-signing-enabled",
                str(config.v2_signing).lower(),
                "--v3-signing-enabled",
                str(config.v3_signing).lower(),
                "--ks",
                str(config.keystore),
                "--ks-pass",
                f"file:{config.keystore_pass_file}",
                "--out",
                str(output_apk),
                str(input_apk),
            ],
        )
        self.console.print("[green]✔ Подпись завершена")

    def _get_package_name_from_manifest(self, decompiled_path: Path) -> str:
        """Читает имя пакета напрямую из AndroidManifest.xml"""
        manifest_path = decompiled_path / "AndroidManifest.xml"
        if not manifest_path.exists():
            return "unknown"

        try:
            tree = ET.parse(manifest_path)
            root = tree.getroot()
            return root.get("package", "unknown")
        except Exception:
            return "unknown"

    def get_meta(self, decompiled: Path = DECOMPILED) -> APKMeta:
        """Извлекает метаданные из декомпилированного APK"""
        apktool_yml = decompiled / "apktool.yml"

        if not apktool_yml.exists():
            raise FileNotFoundError(f"Файл {apktool_yml} не найден")

        with open(apktool_yml, encoding="utf-8") as f:
            meta = yaml.safe_load(f)

        version_info = meta.get("versionInfo", {})

        package_name = self._get_package_name_from_manifest(decompiled)

        if package_name == "unknown":
            package_info = meta_yaml.get("packageInfo", {})
            package_name = package_info.get("renameManifestPackage") or "unknown"

        return APKMeta(
            version_code=version_info.get("versionCode", 0),
            version_name=version_info.get("versionName", "unknown"),
            package=package_name,
            path=decompiled,
        )

    def _extract_package_from_manifest(self, decompiled: Path) -> str | None:
        """Извлекает имя пакета из AndroidManifest.xml"""
        manifest = decompiled / "AndroidManifest.xml"

        if not manifest.exists():
            return None

        try:
            import re

            content = manifest.read_text(encoding="utf-8")
            match = re.search(r'package="([^"]+)"', content)
            if match:
                return match.group(1)
        except Exception:
            pass

        return None

    def build_and_sign(
        self,
        source: Path = DECOMPILED,
        output_dir: Path = MODIFIED,
        signing_config: Optional[SigningConfig] = None,
        cleanup: bool = True,
    ) -> tuple[Path, APKMeta]:
        """
        Полный цикл сборки: компиляция, выравнивание, подпись.
        Возвращает путь к подписанному APK и метаданные.
        """
        meta = self.get_meta(source)

        out_apk = output_dir / meta.output_name
        aligned_apk = output_dir / meta.aligned_name
        signed_apk = output_dir / meta.signed_name

        for f in [out_apk, aligned_apk, signed_apk]:
            f.unlink(missing_ok=True)

        self.compile(source, out_apk)
        self.align(out_apk, aligned_apk)
        self.sign(aligned_apk, signed_apk, signing_config)

        if cleanup:
            out_apk.unlink(missing_ok=True)
            aligned_apk.unlink(missing_ok=True)
            idsig = signed_apk.with_suffix(".apk.idsig")
            idsig.unlink(missing_ok=True)

        self.console.print(f"[green]✔ APK готов: {signed_apk.name}")

        return signed_apk, meta
