from collections import Counter
from pathlib import Path
from rich.table import Table
from rich.prompt import Prompt, Confirm
from typing import Any
from types import ModuleType
from typing_extensions import Annotated
from importlib.metadata import version as get_version
import importlib
import asyncio
import tomllib
import typer
from .common import (
    diff_storage_zone,
    p_warning,
    p_error,
    p_success,
    console,
    CONFIG_NAME,
)


app = typer.Typer()


def version_callback(value: bool) -> None:
    if value:
        v = get_version("trifold")
        console.print(f"trifold {v}")
        raise typer.Exit()


@app.callback()
def common(
    ctx: typer.Context,
    version: bool = typer.Option(None, "--version", callback=version_callback),
) -> None:
    pass


def load_config() -> dict[str, Any]:
    with open(CONFIG_NAME, "rb") as f:
        config = tomllib.load(f)
    provider_name = config["provider"]
    provider = importlib.import_module(f"trifold.{provider_name}")
    provider.augment_config(config)

    return config, provider


def show_plan(file_status_list: list[tuple[str, str]], verbosity: int) -> None:
    counts = Counter()
    if verbosity >= 1:
        table = Table(show_header=True, header_style="bold")
        table.add_column("File")
        table.add_column("Status")
    for filename, status in file_status_list:
        counts[status] += 1
        if verbosity >= 1:
            if status == "only_local":
                table.add_row(filename, f"[yellow]{status}[/yellow]")
            elif status == "only_remote":
                table.add_row(filename, f"[magenta]{status}[/magenta]")
            elif status in ("ignored", "remote_ignored", "same"):
                if verbosity >= 2:
                    table.add_row(filename, f"[dim]{status}[/dim]")
            else:
                table.add_row(filename, f"[blue]{status}[/blue]")
    if verbosity >= 1:
        console.print(table)
    console.print(f"[green]{counts['only_local']} only_local[/green] | ", end="")
    console.print(f"[yellow]{counts['only_remote']} only_remote[/yellow] | ", end="")
    console.print(f"[green]{counts['diff']} diff[/green] | ", end="")
    console.print(f"[blue]{counts['same']} same[/blue]")


async def execute_plan(
    config: dict[str, Any],
    provider: ModuleType,
    file_status_list: list[tuple[str, str]],
    delete: bool = False,
) -> None:
    upload_count = 0
    delete_count = 0

    local_dir = Path.cwd() / config["local_path"]

    tasks = []

    for filename, status in file_status_list:
        match status:
            case "only_local":
                tasks.append(provider.upload_file(config, filename, local_dir))
                upload_count += 1
            case "only_remote":
                if delete:
                    tasks.append(provider.delete_remote_file(config, filename))
                delete_count += 1
            case "diff":
                tasks.append(provider.upload_file(config, filename, local_dir))
                upload_count += 1

    # wait on all tasks
    await asyncio.gather(*tasks)

    # print actions
    p_success(f"\nuploaded: {upload_count}")
    if delete:
        p_warning(f"deleted: {delete_count}")
    else:
        p_warning(f"[yellow]would delete: {delete_count}[/yellow]")


# Typer Commands ##############


@app.command()
def init() -> int | None:
    """
    Initialize a new hosted site & write a trifold.toml
    """
    if (Path.cwd() / CONFIG_NAME).exists():
        p_error(f"{CONFIG_NAME} already exists!")
        return -1
    provider_name = "bunny"
    provider = importlib.import_module(f"trifold.{provider_name}")
    settings = provider.extra_init()

    # paths
    remote_path = Prompt.ask("remote path", default="")
    local_path = Prompt.ask("local path", default="")

    config_content = f"""provider = "{provider_name}"
remote_path = "{remote_path}"
local_path = "{local_path}"
ignore = []
remote_ignore = []

[{provider_name}]
"""
    for k, v in settings.items():
        config_content += f'{k} = "{v}"\n'

    with open(CONFIG_NAME, "w") as f:
        f.write(config_content)

    p_success(f"Created {CONFIG_NAME}")


@app.command()
def status(
    verbose: Annotated[int, typer.Option("--verbose", "-v", count=True)] = 0,
) -> None:
    """
    Show differences between local and remote.
    """
    config, provider = load_config()
    plan = asyncio.run(diff_storage_zone(config, provider))
    url = provider.get_cdn_url(config)
    console.print(f"CDN url: {url}")
    show_plan(plan, verbose)


@app.command()
def hostname_add(hostname: str) -> None:
    """
    Add a custom hostname to the site.
    """
    config, provider = load_config()
    if not Confirm.ask(
        f"does a DNS record already exist pointing {hostname} => "
        f"{provider.get_cdn_url(config)}"
    ):
        p_error("ensure DNS has been set, then try again")
        return
    provider.add_custom_hostname(config, hostname)


@app.command()
def publish(
    delete: bool = False,
    verbose: Annotated[int, typer.Option("--verbose", "-v", count=True)] = 0,
) -> None:
    """
    Publish local files to remote & purge cache.
    """
    config, provider = load_config()

    async def plan_and_run() -> list:
        plan = await diff_storage_zone(config, provider)
        show_plan(plan, verbose)
        await execute_plan(config, provider, plan, delete)
        return plan

    plan = asyncio.run(plan_and_run())
    all_files = [f[0] for f in plan]
    # if a 404.html is added and no error page is set, fix that
    if "404.html" in all_files and not config["custom_404_page"]:
        p_success("using custom 404 page: 404.html")
        # TODO: this is going to need to change for other providers
        provider.set_404_page(config, "404.html")
    provider.purge_cache(config)
    url = provider.get_cdn_url(config)
    p_success(f"cache purged! {url}")


@app.command()
def sites() -> None:
    """
    Show all sites and their current status.
    """
    provider_name = "bunny"
    provider = importlib.import_module(f"trifold.{provider_name}")
    provider.show_sites()


@app.command()
def redirects() -> None:
    """
    Experimental support for adding redirects.
    """
    config, provider = load_config()
    provider.create_redirects(config)


if __name__ == "__main__":
    app()
