__version__ = "1.0.0"
import shutil
from functools import wraps
from pathlib import Path
from typing import Any, Dict, List

import typer
from plumbum import ProcessExecutionError, local
from rich.console import Console
from rich.progress import Progress
from rich.prompt import Prompt
from rich.table import Table

from utils.apk import APKMeta, APKProcessor
from utils.config import Config, PatchTemplate, load_config
from utils.info import print_model_fields, print_model_table
from utils.patch_manager import (BuildError, ConfigError, PatcherError,
                                 PatchManager, handle_errors)
from utils.tools import (CONFIGS, DECOMPILED, MODIFIED, ORIGINAL, PATCHES,
                         TOOLS, download, ensure_dirs, run, select_apk)

console = Console()
app = typer.Typer(
    name="anixarts-patcher",
    help="Инструмент для модификации Anixarty APK",
    add_completion=False,
)


from datetime import datetime


def generate_report(
    apk_path: Path,
    meta: APKMeta,
    patches: List[PatchTemplate],
    manager: PatchManager,
) -> None:
    """Генерирует отчёт о сборке в формате Markdown"""
    report_path = MODIFIED / "report.md"

    applied_count = sum(1 for p in patches if p.applied)
    applied_patches = [p for p in patches if p.applied]
    failed_patches = [p for p in patches if not p.applied]

    applied_patches.sort(key=lambda p: p.priority, reverse=True)
    failed_patches.sort(key=lambda p: p.priority, reverse=True)

    def get_patch_info(patch: PatchTemplate) -> Dict[str, str]:
        """Получает описание и автора патча из модуля"""
        info = {"doc": "", "author": "-"}
        try:
            patch_module = manager.load_patch_module(patch.name)
            doc = patch_module.__doc__
            if doc:
                info["doc"] = doc.strip().split("\n")[0]
            author = getattr(patch_module, "__author__", "")
            if author:
                info["author"] = f"`{author}`"
        except Exception:
            pass
        return info

    lines = []
    lines.append(f"Anixarts {meta.version_name} (build {meta.version_code})")
    lines.append("")

    lines.append("## 📦 Информация о сборке")
    lines.append("")
    lines.append("| Параметр | Значение |")
    lines.append("|----------|----------|")
    lines.append(f"| Версия | `{meta.version_name}` |")
    lines.append(f"| Код версии | `{meta.version_code}` |")
    lines.append(f"| Пакет | `{meta.package}` |")
    lines.append(f"| Файл | `{apk_path.name}` |")
    lines.append(f"| Дата сборки | {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} |")
    lines.append("")

    lines.append("## 🔧 Применённые патчи")
    lines.append("")

    if applied_patches:
        lines.append(
            f"> ✅ Успешно применено: **{applied_count}** из **{len(patches)}**"
        )
        lines.append("")
        lines.append("| Патч | Приоритет | Автор | Описание |")
        lines.append("|------|:---------:|-------|----------|")

        for p in applied_patches:
            info = get_patch_info(p)
            lines.append(
                f"| ✅ `{p.name}` | {p.priority} | {info['author']} | {info['doc']} |"
            )
    else:
        lines.append("> ⚠️ Нет применённых патчей")

    lines.append("")

    if failed_patches:
        lines.append("## ❌ Ошибки")
        lines.append("")
        lines.append("| Патч | Приоритет | Автор | Описание |")
        lines.append("|------|:---------:|-------|----------|")

        for p in failed_patches:
            info = get_patch_info(p)
            lines.append(
                f"| ❌ `{p.name}` | {p.priority} | {info['author']} | {info['doc']} |"
            )

        lines.append("")

    lines.append("---")
    lines.append("")
    lines.append(
        "*Собрано с помощью [anixarts-patcher](https://git.0x174.su/anixart-mod/patcher)*"
    )

    report_path.write_text("\n".join(lines), encoding="utf-8")
    console.print(f"[dim]Отчёт сохранён: {report_path}[/dim]")


# ========================= COMMANDS =========================
@app.command()
@handle_errors
def init():
    """Инициализация: создание директорий и скачивание инструментов"""
    ensure_dirs()

    conf = load_config(console)

    # Проверка Java
    console.print("[cyan]Проверка Java...")
    try:
        local["java"]["-version"].run(retcode=None)
        console.print("[green]✔ Java найдена")
    except ProcessExecutionError:
        raise PatcherError("Java не установлена. Установите JDK 11+")

    # Скачивание apktool
    apktool_jar = TOOLS / "apktool.jar"
    if not apktool_jar.exists():
        download(console, conf.tools.apktool_jar_url, apktool_jar)
    else:
        console.print(f"[dim]✔ {apktool_jar.name} уже существует[/dim]")

    # Скачивание apktool wrapper
    apktool_wrapper = TOOLS / "apktool"
    if not apktool_wrapper.exists():
        download(console, conf.tools.apktool_wrapper_url, apktool_wrapper)
        apktool_wrapper.chmod(0o755)
    else:
        console.print(f"[dim]✔ {apktool_wrapper.name} уже существует[/dim]")

    # Проверка zipalign и apksigner
    for tool in ["zipalign", "apksigner"]:
        try:
            local[tool]["--version"].run(retcode=None)
            console.print(f"[green]✔ {tool} найден")
        except Exception:
            console.print(f"[yellow]⚠ {tool} не найден в PATH")

    # Проверка keystore
    if not Path("keystore.jks").exists():
        console.print("[yellow]⚠ keystore.jks не найден. Создайте его командой:")
        console.print(
            "[dim]  keytool -genkey -v -keystore keystore.jks -keyalg RSA "
            "-keysize 2048 -validity 10000 -alias key[/dim]"
        )

    # Инициализация конфигов патчей
    console.print("\n[cyan]Инициализация конфигураций патчей...")
    manager = PatchManager(console)

    for name in manager.discover_patches():
        patch = manager.load_patch(name)
        config_path = CONFIGS / f"{name}.json"
        if not config_path.exists():
            patch.save_config()
            console.print(f"  [green]✔ {name}.json создан")
        else:
            console.print(f"  [dim]✔ {name}.json существует[/dim]")

    console.print("\n[green]✔ Инициализация завершена")


@app.command("list")
@handle_errors
def list_patches():
    """Показать список всех патчей"""
    manager = PatchManager(console)
    all_patches = manager.discover_all()

    table = Table(title="Доступные патчи")
    table.add_column("Приоритет", justify="center", style="cyan")
    table.add_column("Название", style="yellow")
    table.add_column("Статус", justify="center")
    table.add_column("Автор", style="magenta")
    table.add_column("Версия", style="yellow")
    table.add_column("Описание")

    patch_rows = []

    for name in all_patches["ready"]:
        try:
            patch = manager.load_patch(name)
            status = "[green]✔ вкл[/green]" if patch.enabled else "[red]✘ выкл[/red]"
            patch_class = manager.load_patch_class(name)
            priority = getattr(patch_class, "priority", 0)
            patch_module = manager.load_patch_module(name)
            author = getattr(patch_module, "__author__", "")
            version = getattr(patch_module, "__version__", "")
            description = (patch_module.__doc__ or "").strip().split("\n")[0]
            patch_rows.append(
                (patch.priority, name, status, author, version, description)
            )
        except Exception as e:
            raise e
            patch_rows.append((0, name, "[red]⚠ ошибка[/red]", "", "", str(e)[:40]))

    for name in all_patches["todo"]:
        try:
            patch_class = manager.load_patch_class(name)
            priority = getattr(patch_class, "priority", 0)
            patch_module = manager.load_patch_module(name)
            author = getattr(patch_module, "__author__", "")
            version = getattr(patch_module, "__version__", "")
            description = (patch_module.__doc__ or "").strip().split("\n")[0]
            patch_rows.append(
                (
                    priority,
                    name,
                    "[yellow]⚠ todo[/yellow]",
                    author,
                    version,
                    description,
                )
            )
        except Exception:
            patch_rows.append((0, name, "[yellow]⚠ todo[/yellow]", "", "", ""))

    patch_rows.sort(key=lambda x: x[0], reverse=True)

    for priority, name, status, author, version, desc in patch_rows:
        table.add_row(str(priority), name, status, author, version, desc[:50])

    console.print(table)


@app.command()
@handle_errors
def info(
    patch_name: str = typer.Argument(..., help="Имя патча"),
    tree: bool = typer.Option(False, "--tree", "-t", help="Древовидный вывод полей"),
):
    """Показать подробную информацию о патче"""
    manager = PatchManager(console)

    all_patches = manager.discover_all()
    all_names = all_patches["ready"] + all_patches["todo"]

    if patch_name not in all_names:
        raise PatcherError(f"Патч '{patch_name}' не найден")

    patch_class = manager.load_patch_class(patch_name)

    console.print(f"\n[bold cyan]Патч: {patch_name}[/bold cyan]")
    console.print("-" * 50)

    if patch_class.__doc__:
        console.print(f"[white]{patch_class.__doc__.strip()}[/white]\n")

    is_todo = patch_name in all_patches["todo"]
    if is_todo:
        console.print("[yellow]Статус: в разработке[/yellow]\n")
    else:
        patch = manager.load_patch(patch_name)
        status = "[green]включён[/green]" if patch.enabled else "[red]выключен[/red]"
        console.print(f"Статус: {status}")
        console.print(f"Приоритет: [cyan]{patch.priority}[/cyan]\n")

    console.print("[bold]Поля конфигурации:[/bold]")

    if tree:
        print_model_fields(console, patch_class)
    else:
        table = print_model_table(console, patch_class)
        console.print(table)

    table = Table(show_header=True)
    table.add_column("Поле", style="yellow")
    table.add_column("Тип", style="cyan")
    table.add_column("По умолчанию")
    table.add_column("Описание")

    for field_name, field_info in patch_class.model_fields.items():
        field_type = getattr(
            field_info.annotation, "__name__", str(field_info.annotation)
        )
        default = str(field_info.default) if field_info.default is not None else "-"
        description = field_info.description or ""
        table.add_row(field_name, field_type, default, description)

    console.print(table)

    if not is_todo:
        config_path = CONFIGS / f"{patch_name}.json"
        if config_path.exists():
            console.print(f"\n[bold]Текущая конфигурация[/bold] ({config_path}):")
            console.print(config_path.read_text())


@app.command()
@handle_errors
def enable(patch_name: str = typer.Argument(..., help="Имя патча")):
    """Включить патч"""
    manager = PatchManager(console)

    if patch_name not in manager.discover_patches():
        raise PatcherError(f"Патч '{patch_name}' не найден")

    patch = manager.load_patch(patch_name)
    patch.enabled = True
    patch.save_config()

    console.print(f"[green]✔ Патч {patch_name} включён")


@app.command()
@handle_errors
def disable(patch_name: str = typer.Argument(..., help="Имя патча")):
    """Выключить патч"""
    manager = PatchManager(console)

    if patch_name not in manager.discover_patches():
        raise PatcherError(f"Патч '{patch_name}' не найден")

    patch = manager.load_patch(patch_name)
    patch.enabled = False
    patch.save_config()

    console.print(f"[yellow]✔ Патч {patch_name} выключен")


@app.command()
@handle_errors
def build(
    force: bool = typer.Option(
        False, "--force", "-f", help="Принудительная сборка при ошибках"
    ),
    verbose: bool = typer.Option(False, "--verbose", "-v", help="Подробный вывод"),
    skip_compile: bool = typer.Option(
        False, "--skip-compile", "-s", help="Пропустить компиляцию (только патчи)"
    ),
):
    """Декомпиляция, применение патчей и сборка APK"""
    conf = load_config(console)

    apk_processor = APKProcessor(console, TOOLS)

    apk = select_apk(console)
    apk_processor.decompile(apk, DECOMPILED)

    manager = PatchManager(console)
    patches = manager.load_enabled_patches()

    if not patches:
        console.print("[yellow]Нет включённых патчей")
        if not force:
            raise typer.Exit(0)

    base_config = conf.base.copy()
    base_config["verbose"] = verbose
    base_config["decompiled"] = str(DECOMPILED)

    console.print(f"\n[cyan]Применение патчей ({len(patches)})...[/cyan]")

    with Progress(console=console) as progress:
        task = progress.add_task("Патчи", total=len(patches))

        for patch in patches:
            success = patch.safe_apply(base_config)
            status = "[green]✔[/green]" if success else "[red]✘[/red]"
            progress.console.print(f"  {status} [{patch.priority:2d}] {patch.name}")
            progress.advance(task)

    applied = sum(1 for p in patches if p.applied)
    failed = len(patches) - applied

    console.print()
    if failed == 0:
        console.print(f"[green]✔ Все патчи применены ({applied}/{len(patches)})")
    else:
        console.print(
            f"[yellow]⚠ Применено: {applied}/{len(patches)}, ошибок: {failed}"
        )

    if skip_compile:
        console.print("[yellow]Компиляция пропущена (--skip-compile)")
        return

    should_compile = (
        failed == 0
        or force
        or Prompt.ask(
            "\nПродолжить сборку несмотря на ошибки?", choices=["y", "n"], default="n"
        )
        == "y"
    )

    if should_compile:
        console.print()
        signed_apk, meta = apk_processor.build_and_sign(
            source=DECOMPILED,
            output_dir=MODIFIED,
        )
        generate_report(signed_apk, meta, patches, manager)
    else:
        console.print("[red]Сборка отменена")
        raise typer.Exit(1)


@app.command()
@handle_errors
def clean(
    all_dirs: bool = typer.Option(
        False, "--all", "-a", help="Очистить все директории включая modified и configs"
    )
):
    """Очистка временных файлов"""
    dirs_to_clean = [DECOMPILED]

    if all_dirs:
        dirs_to_clean.extend([MODIFIED, CONFIGS])

    for d in dirs_to_clean:
        if d.exists():
            shutil.rmtree(d)
            d.mkdir()
            console.print(f"[yellow]✔ Очищено: {d}")
        else:
            console.print(f"[dim]≫ Пропущено (не существует): {d}[/dim]")

    console.print("[green]✔ Очистка завершена")


@app.command()
@handle_errors
def config():
    """Показать текущую конфигурацию"""
    conf = load_config(console)

    console.print("[bold cyan]Конфигурация (config.json):[/bold cyan]\n")

    console.print("[yellow]Tools:[/yellow]")
    console.print(f"  apktool_jar_url: {conf.tools.apktool_jar_url}")
    console.print(f"  apktool_wrapper_url: {conf.tools.apktool_wrapper_url}")

    if conf.base:
        console.print("\n[yellow]Base:[/yellow]")
        for key, value in conf.base.items():
            console.print(f"  {key}: {value}")


@app.command()
@handle_errors
def version():
    """Показать версию инструмента"""
    console.print(f"[cyan]anixarts-patcher[/cyan] v{__version__}")


if __name__ == "__main__":
    app()
