"""Click-based CLI — all user-facing commands."""

from __future__ import annotations

import sys
from typing import Optional

import click
from rich import box
from rich.console import Console
from rich.panel import Panel
from rich.table import Table

from . import __version__
from .config import Config
from .curseforge import (
    CurseForgeAuthError,
    CurseForgeClient,
    CurseForgeError,
    CurseForgeNotFoundError,
)
from .database import Database
from .notifier import DiscordNotifier, NotificationError

console = Console()


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _load_db(cfg: Config) -> Database:
    return Database(cfg.database_path)


def _require_client(cfg: Config) -> CurseForgeClient:
    """Return a configured API client, or exit with a helpful message."""
    if not cfg.curseforge_api_key:
        console.print("[red]Error:[/red] No CurseForge API key configured.")
        console.print(
            "Set one with: [bold]modpack-checker config set-key YOUR_KEY[/bold]"
        )
        console.print(
            "Get a free key at: [dim]https://console.curseforge.com[/dim]"
        )
        sys.exit(1)
    return CurseForgeClient(cfg.curseforge_api_key)


# ---------------------------------------------------------------------------
# Root group
# ---------------------------------------------------------------------------


@click.group(context_settings={"help_option_names": ["-h", "--help"]})
@click.version_option(version=__version__, prog_name="modpack-checker")
def cli() -> None:
    """Modpack Version Checker — monitor CurseForge modpacks for updates.

    \b
    Quick start:
      modpack-checker config set-key  YOUR_CURSEFORGE_KEY
      modpack-checker add             238222
      modpack-checker check
    """


# ---------------------------------------------------------------------------
# config sub-group
# ---------------------------------------------------------------------------


@cli.group("config")
def config_group() -> None:
    """Manage configuration (API key, webhook URL, etc.)."""


@config_group.command("set-key")
@click.argument("api_key")
def config_set_key(api_key: str) -> None:
    """Save your CurseForge API key and validate it."""
    api_key = api_key.strip()
    if not api_key:
        console.print("[red]Error:[/red] API key cannot be empty.")
        sys.exit(1)
    cfg = Config.load()
    cfg.curseforge_api_key = api_key
    cfg.save()

    client = CurseForgeClient(api_key)
    with console.status("Validating API key with CurseForge…"):
        valid = client.validate_api_key()

    if valid:
        console.print("[green]✓[/green] API key saved and validated.")
    else:
        console.print(
            "[yellow]⚠[/yellow] API key saved, but validation failed. "
            "Double-check at [dim]https://console.curseforge.com[/dim]"
        )


@config_group.command("set-webhook")
@click.argument("webhook_url")
def config_set_webhook(webhook_url: str) -> None:
    """Save a Discord webhook URL and send a test message."""
    cfg = Config.load()
    cfg.discord_webhook_url = webhook_url.strip()
    cfg.save()

    notifier = DiscordNotifier(webhook_url)
    try:
        with console.status("Testing webhook…"):
            notifier.test()
        console.print("[green]✓[/green] Webhook saved and test message sent.")
    except NotificationError as exc:
        console.print(f"[yellow]⚠[/yellow] Webhook saved, but test failed: {exc}")


@config_group.command("set-interval")
@click.argument("hours", type=int)
def config_set_interval(hours: int) -> None:
    """Set how often the scheduler checks for updates (in hours, 1–168)."""
    if not 1 <= hours <= 168:
        console.print("[red]Error:[/red] Interval must be between 1 and 168 hours.")
        sys.exit(1)
    cfg = Config.load()
    cfg.check_interval_hours = hours
    cfg.save()
    console.print(f"[green]✓[/green] Check interval set to {hours} hour(s).")


@config_group.command("show")
def config_show() -> None:
    """Display the current configuration."""
    cfg = Config.load()

    key = cfg.curseforge_api_key
    if key and len(key) > 8:
        masked = f"{key[:4]}{'*' * (len(key) - 8)}{key[-4:]}"
    elif key:
        masked = "****"
    else:
        masked = "[red]Not configured[/red]"

    table = Table(title="Configuration", box=box.ROUNDED, show_header=False)
    table.add_column("Setting", style="cyan", min_width=22)
    table.add_column("Value")

    table.add_row("CurseForge API Key", masked)
    table.add_row(
        "Discord Webhook",
        cfg.discord_webhook_url or "[dim]Not configured[/dim]",
    )
    table.add_row("Database", cfg.database_path)
    table.add_row("Check Interval", f"{cfg.check_interval_hours} hour(s)")
    table.add_row(
        "Notifications",
        "[green]On[/green]" if cfg.notification_on_update else "[red]Off[/red]",
    )

    console.print(table)


# ---------------------------------------------------------------------------
# add
# ---------------------------------------------------------------------------


@cli.command()
@click.argument("modpack_id", type=int)
def add(modpack_id: int) -> None:
    """Add a modpack to the watch list by its CurseForge project ID.

    \b
    Example:
      modpack-checker add 238222   # All The Mods 9
    """
    cfg = Config.load()
    client = _require_client(cfg)
    db = _load_db(cfg)

    with console.status(f"Looking up modpack {modpack_id} on CurseForge…"):
        try:
            name = client.get_mod_name(modpack_id)
        except CurseForgeNotFoundError:
            console.print(
                f"[red]Error:[/red] No modpack found with ID {modpack_id} on CurseForge."
            )
            sys.exit(1)
        except CurseForgeAuthError as exc:
            console.print(f"[red]Auth error:[/red] {exc}")
            sys.exit(1)
        except CurseForgeError as exc:
            console.print(f"[red]API error:[/red] {exc}")
            sys.exit(1)

    try:
        db.add_modpack(modpack_id, name)
    except ValueError:
        console.print(
            f"[yellow]Already tracked:[/yellow] [bold]{name}[/bold] (ID: {modpack_id})"
        )
        return

    console.print(
        f"[green]✓[/green] Now tracking [bold]{name}[/bold] (ID: {modpack_id})"
    )
    console.print("Run [bold]modpack-checker check[/bold] to fetch the current version.")


# ---------------------------------------------------------------------------
# remove
# ---------------------------------------------------------------------------


@cli.command()
@click.argument("modpack_id", type=int)
@click.confirmation_option(prompt="Remove this modpack and all its history?")
def remove(modpack_id: int) -> None:
    """Remove a modpack from the watch list."""
    cfg = Config.load()
    db = _load_db(cfg)

    modpack = db.get_modpack(modpack_id)
    if modpack is None:
        console.print(
            f"[red]Error:[/red] Modpack ID {modpack_id} is not in your watch list."
        )
        sys.exit(1)

    db.remove_modpack(modpack_id)
    console.print(f"[green]✓[/green] Removed [bold]{modpack.name}[/bold].")


# ---------------------------------------------------------------------------
# list
# ---------------------------------------------------------------------------


@cli.command(name="list")
def list_modpacks() -> None:
    """Show all watched modpacks and their last known versions."""
    cfg = Config.load()
    db = _load_db(cfg)
    modpacks = db.get_all_modpacks()

    if not modpacks:
        console.print("[dim]Watch list is empty.[/dim]")
        console.print(
            "Add a modpack with: [bold]modpack-checker add <curseforge-id>[/bold]"
        )
        return

    table = Table(
        title=f"Watched Modpacks ({len(modpacks)})",
        box=box.ROUNDED,
    )
    table.add_column("CF ID", style="cyan", justify="right", no_wrap=True)
    table.add_column("Name", style="bold white")
    table.add_column("Current Version", style="green")
    table.add_column("Last Checked", style="dim")
    table.add_column("Alerts", justify="center")

    for mp in modpacks:
        last_checked = (
            mp.last_checked.strftime("%Y-%m-%d %H:%M")
            if mp.last_checked
            else "[dim]Never[/dim]"
        )
        alerts = "[green]✓[/green]" if mp.notification_enabled else "[red]✗[/red]"
        version = mp.current_version or "[dim]—[/dim]"
        table.add_row(str(mp.curseforge_id), mp.name, version, last_checked, alerts)

    console.print(table)


# ---------------------------------------------------------------------------
# check
# ---------------------------------------------------------------------------


@cli.command()
@click.option(
    "--id", "-m", "modpack_id", type=int, default=None,
    help="Check a single modpack by CurseForge ID.",
)
@click.option(
    "--notify/--no-notify", default=True,
    help="Send Discord notifications for updates found (default: on).",
)
def check(modpack_id: Optional[int], notify: bool) -> None:
    """Check all (or one) watched modpack(s) for updates."""
    cfg = Config.load()
    client = _require_client(cfg)
    db = _load_db(cfg)

    if modpack_id is not None:
        target = db.get_modpack(modpack_id)
        if target is None:
            console.print(
                f"[red]Error:[/red] Modpack ID {modpack_id} is not in your watch list."
            )
            sys.exit(1)
        modpacks = [target]
    else:
        modpacks = db.get_all_modpacks()

    if not modpacks:
        console.print("[dim]No modpacks to check.[/dim]")
        console.print(
            "Add one with: [bold]modpack-checker add <curseforge-id>[/bold]"
        )
        return

    notifier: Optional[DiscordNotifier] = None
    if notify and cfg.discord_webhook_url and cfg.notification_on_update:
        notifier = DiscordNotifier(cfg.discord_webhook_url)

    updates = 0
    errors = 0

    for mp in modpacks:
        with console.status(f"Checking [bold]{mp.name}[/bold]…"):
            try:
                file_obj = client.get_latest_file(mp.curseforge_id)
            except CurseForgeNotFoundError:
                console.print(
                    f"  [red]✗[/red] {mp.name}: not found on CurseForge "
                    f"(ID: {mp.curseforge_id})"
                )
                errors += 1
                continue
            except CurseForgeError as exc:
                console.print(f"  [red]✗[/red] {mp.name}: API error — {exc}")
                errors += 1
                continue

        if file_obj is None:
            console.print(f"  [yellow]⚠[/yellow] {mp.name}: no files found on CurseForge.")
            errors += 1
            continue

        new_version = client.extract_version(file_obj)
        notification_sent = False

        if mp.current_version == new_version:
            line = f"[green]✓[/green] {mp.name}: up to date ([bold]{new_version}[/bold])"
        elif mp.current_version is None:
            line = (
                f"[cyan]→[/cyan] {mp.name}: "
                f"initial version recorded as [bold]{new_version}[/bold]"
            )
        else:
            updates += 1
            line = (
                f"[yellow]↑[/yellow] {mp.name}: "
                f"[dim]{mp.current_version}[/dim] → [bold green]{new_version}[/bold green]"
            )
            if notifier and mp.notification_enabled:
                try:
                    notifier.send_update(
                        mp.name, mp.curseforge_id, mp.current_version, new_version
                    )
                    notification_sent = True
                    line += " [dim](notified)[/dim]"
                except NotificationError as exc:
                    line += f" [red](notification failed: {exc})[/red]"

        db.update_version(mp.curseforge_id, new_version, notification_sent)
        console.print(f"  {line}")

    # Summary line
    console.print()
    parts = []
    if updates:
        parts.append(f"[yellow]{updates} update(s) found[/yellow]")
    if errors:
        parts.append(f"[red]{errors} error(s)[/red]")
    if not updates and not errors:
        parts.append("[green]All modpacks are up to date.[/green]")
    console.print("  ".join(parts))


# ---------------------------------------------------------------------------
# status
# ---------------------------------------------------------------------------


@cli.command()
@click.argument("modpack_id", type=int)
@click.option("--limit", "-n", default=10, show_default=True, help="History entries to show.")
def status(modpack_id: int, limit: int) -> None:
    """Show detailed status and check history for a modpack."""
    cfg = Config.load()
    db = _load_db(cfg)

    mp = db.get_modpack(modpack_id)
    if mp is None:
        console.print(
            f"[red]Error:[/red] Modpack ID {modpack_id} is not in your watch list."
        )
        sys.exit(1)

    last_checked = (
        mp.last_checked.strftime("%Y-%m-%d %H:%M UTC") if mp.last_checked else "Never"
    )
    notif_str = "[green]Enabled[/green]" if mp.notification_enabled else "[red]Disabled[/red]"

    console.print(
        Panel(
            f"[bold white]{mp.name}[/bold white]\n"
            f"CurseForge ID : [cyan]{mp.curseforge_id}[/cyan]\n"
            f"Version       : [green]{mp.current_version or 'Not checked yet'}[/green]\n"
            f"Last Checked  : [dim]{last_checked}[/dim]\n"
            f"Notifications : {notif_str}",
            title="Modpack Status",
            border_style="cyan",
        )
    )

    history = db.get_check_history(modpack_id, limit=limit)
    if not history:
        console.print("[dim]No check history yet.[/dim]")
        return

    table = Table(title=f"Check History (last {limit})", box=box.SIMPLE)
    table.add_column("Timestamp", style="dim")
    table.add_column("Version", style="green")
    table.add_column("Notified", justify="center")

    for entry in history:
        notified = "[green]✓[/green]" if entry.notification_sent else "[dim]—[/dim]"
        table.add_row(
            entry.checked_at.strftime("%Y-%m-%d %H:%M"),
            entry.version_found or "[dim]Unknown[/dim]",
            notified,
        )

    console.print(table)


# ---------------------------------------------------------------------------
# notifications (toggle per-modpack alerts)
# ---------------------------------------------------------------------------


@cli.command()
@click.argument("modpack_id", type=int)
@click.option(
    "--enable/--disable",
    default=True,
    help="Enable or disable Discord alerts for this modpack.",
)
def notifications(modpack_id: int, enable: bool) -> None:
    """Enable or disable Discord notifications for a specific modpack."""
    cfg = Config.load()
    db = _load_db(cfg)

    mp = db.get_modpack(modpack_id)
    if mp is None:
        console.print(
            f"[red]Error:[/red] Modpack ID {modpack_id} is not in your watch list."
        )
        sys.exit(1)

    db.toggle_notifications(modpack_id, enable)
    state = "[green]enabled[/green]" if enable else "[red]disabled[/red]"
    console.print(
        f"[green]✓[/green] Notifications {state} for [bold]{mp.name}[/bold]."
    )


# ---------------------------------------------------------------------------
# schedule (background daemon)
# ---------------------------------------------------------------------------


@cli.command()
@click.option(
    "--hours", "-h", "hours", type=int, default=None,
    help="Override the configured check interval (hours).",
)
def schedule(hours: Optional[int]) -> None:
    """Run continuous background checks on a configurable interval.

    Requires the [scheduler] extra:  pip install modpack-version-checker[scheduler]
    """
    try:
        from apscheduler.schedulers.blocking import BlockingScheduler
    except ImportError:
        console.print("[red]Error:[/red] APScheduler is not installed.")
        console.print(
            "Install it with: [bold]pip install modpack-version-checker[scheduler][/bold]"
        )
        sys.exit(1)

    cfg = Config.load()
    interval = hours or cfg.check_interval_hours

    def _run_check() -> None:
        """Inner function executed by the scheduler."""
        client = _require_client(cfg)
        db = _load_db(cfg)
        modpacks = db.get_all_modpacks()

        notifier: Optional[DiscordNotifier] = None
        if cfg.discord_webhook_url and cfg.notification_on_update:
            notifier = DiscordNotifier(cfg.discord_webhook_url)

        for mp in modpacks:
            try:
                file_obj = client.get_latest_file(mp.curseforge_id)
                if file_obj is None:
                    continue
                new_version = client.extract_version(file_obj)
                notification_sent = False

                if (
                    mp.current_version is not None
                    and mp.current_version != new_version
                    and notifier
                    and mp.notification_enabled
                ):
                    try:
                        notifier.send_update(
                            mp.name, mp.curseforge_id, mp.current_version, new_version
                        )
                        notification_sent = True
                    except NotificationError:
                        pass

                db.update_version(mp.curseforge_id, new_version, notification_sent)
            except CurseForgeError:
                pass  # Log silently in daemon mode; don't crash the scheduler

    scheduler = BlockingScheduler()
    scheduler.add_job(_run_check, "interval", hours=interval)

    console.print(
        Panel(
            f"Checking every [bold]{interval}[/bold] hour(s).\n"
            "Press [bold]Ctrl-C[/bold] to stop.",
            title="Modpack Checker — Scheduler Running",
            border_style="green",
        )
    )

    # Run immediately so the user gets instant feedback
    _run_check()

    try:
        scheduler.start()
    except KeyboardInterrupt:
        console.print("\n[dim]Scheduler stopped.[/dim]")


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------


def main() -> None:
    cli()


if __name__ == "__main__":
    main()
