from __future__ import annotations

import os
import re
import shutil
import subprocess
from typing import Annotated

import huggingface_hub
from rich import print
from typer import Option

import gradio as gr

repo_directory = os.getcwd()
readme_file = os.path.join(repo_directory, "README.md")
github_action_template = os.path.join(
    os.path.dirname(__file__), "deploy_space_action.yaml"
)


def add_configuration_to_readme(
    title: str | None,
    app_file: str | None,
) -> dict:
    configuration = {}

    dir_name = os.path.basename(repo_directory)
    if title is None:
        title = input(f"Enter Spaces app title [{dir_name}]: ") or dir_name
    formatted_title = format_title(title)
    if formatted_title != title:
        print(f"Formatted to {formatted_title}. ")
    configuration["title"] = formatted_title

    if app_file is None:
        for file in os.listdir(repo_directory):
            file_path = os.path.join(repo_directory, file)
            if not os.path.isfile(file_path) or not file.endswith(".py"):
                continue

            with open(file_path, encoding="utf-8", errors="ignore") as f:
                content = f.read()
                if "import gradio" in content:
                    app_file = file
                    break

        app_file = (
            input(f"Enter Gradio app file {f'[{app_file}]' if app_file else ''}: ")
            or app_file
        )
    if not app_file or not os.path.exists(app_file):
        raise FileNotFoundError("Failed to find Gradio app file.")
    configuration["app_file"] = app_file

    configuration["sdk"] = "gradio"
    configuration["sdk_version"] = gr.__version__
    huggingface_hub.metadata_save(readme_file, configuration)

    configuration["hardware"] = (
        input(
            f"Enter Spaces hardware ({', '.join(hardware.value for hardware in huggingface_hub.SpaceHardware)}) [cpu-basic]: "
        )
        or "cpu-basic"
    )

    secrets = {}
    if input("Any Spaces secrets (y/n) [n]: ") == "y":
        while True:
            secret_name = input("Enter secret name (leave blank to end): ")
            if not secret_name:
                break
            secret_value = input(f"Enter secret value for {secret_name}: ")
            secrets[secret_name] = secret_value
    configuration["secrets"] = secrets

    requirements_file = os.path.join(repo_directory, "requirements.txt")
    if (
        not os.path.exists(requirements_file)
        and input("Create requirements.txt file? (y/n) [n]: ").lower() == "y"
    ):
        while True:
            requirement = input("Enter a dependency (leave blank to end): ")
            if not requirement:
                break
            with open(requirements_file, "a", encoding="utf-8") as f:
                f.write(requirement + "\n")

    if (
        input(
            "Create Github Action to automatically update Space on 'git push'? [n]: "
        ).lower()
        == "y"
    ):
        track_branch = input("Enter branch to track [main]: ") or "main"
        github_action_file = os.path.join(
            repo_directory, ".github/workflows/update_space.yml"
        )
        os.makedirs(os.path.dirname(github_action_file), exist_ok=True)
        with open(github_action_template, encoding="utf-8") as f:
            github_action_content = f.read()
        github_action_content = github_action_content.replace("$branch", track_branch)
        with open(github_action_file, "w", encoding="utf-8") as f:
            f.write(github_action_content)

        print(
            "Github Action created. Add your Hugging Face write token (from https://huggingface.co/settings/tokens) as an Actions Secret named 'hf_token' to your GitHub repository. This can be set in your repository's settings page."
        )

    return configuration


def format_title(title: str):
    title = title.replace(" ", "_")
    title = re.sub(r"[^a-zA-Z0-9\-._]", "", title)
    title = re.sub("-+", "-", title)
    while title.startswith("."):
        title = title[1:]
    return title


def check_gcloud_auth():
    """Check if user is logged in to Google Cloud and has a project selected."""
    try:
        auth_result = subprocess.run(
            ["gcloud", "auth", "list", "--filter=status:ACTIVE"],
            check=True,
            text=True,
            capture_output=True,
        )

        if not auth_result.stdout.strip():
            print("[bold yellow]You are not logged in to Google Cloud.[/bold yellow]")
            print("Running 'gcloud init' to set up authentication...")
            subprocess.run(["gcloud", "init"], check=True)

        project_result = subprocess.run(
            ["gcloud", "config", "get-value", "project"],
            check=True,
            text=True,
            capture_output=True,
        )

        if not project_result.stdout.strip():
            print("[bold yellow]No Google Cloud project is selected.[/bold yellow]")
            project_id = input("Enter your Google Cloud project ID: ")
            if not project_id:
                print("[red]Project ID is required for deployment.[/red]")
                return None
            return project_id

        print(f"[green]✓ Authenticated as: {auth_result.stdout.strip()}[/green]")
        print(f"[green]✓ Project: {project_result.stdout.strip()}[/green]")
        return project_result.stdout.strip()

    except subprocess.CalledProcessError as e:
        print(f"[bold red]Error checking Google Cloud configuration: {e}[/bold red]")
        print("Running 'gcloud init' to set up configuration...")
        try:
            subprocess.run(["gcloud", "init"], check=True)
        except subprocess.CalledProcessError as init_error:
            print(f"[red]Failed to run 'gcloud init': {init_error}[/red]")
            return False
    except FileNotFoundError:
        print(
            "[bold red]gcloud CLI not found. Please install Google Cloud SDK.[/bold red]"
        )
        return False

    return True


def deploy_to_gcloud():
    """Deploy a Gradio app to Google Cloud Run. Always uses app.py as the entry point."""
    if not shutil.which("gcloud"):
        print(
            "[bold red]gcloud CLI is not installed.[/bold red]\n"
            "Please install the Google Cloud SDK from: "
            "[link]https://cloud.google.com/sdk/docs/install[/link]"
        )
        return

    project_id = check_gcloud_auth()
    if not project_id:
        print(
            "[bold red]Google Cloud configuration failed. Please run 'gcloud init' manually.[/bold red]"
        )
        return

    if not os.path.exists("app.py") and not os.path.exists("main.py"):
        print(
            "[bold red]Error:[/bold red] app.py and main.py not found. Google Cloud Run deployment requires app.py or main.py as the entry point."
        )
        return

    requirements_file = "requirements.txt"
    if not os.path.exists(requirements_file):
        if (
            input(
                f"A requirements.txt file is necessary for Google Cloud Run deployment. Create requirements.txt with gradio=={gr.__version__}? (y/n) [y]: "
            ).lower()
            != "n"
        ):
            with open(requirements_file, "w", encoding="utf-8") as f:
                f.write(f"gradio=={gr.__version__}\n")
            print(f"Created requirements.txt with gradio=={gr.__version__}")
        else:
            print("\n[yellow]Deployment cancelled.[/yellow]")
            return
    else:
        with open(requirements_file, encoding="utf-8") as f:
            requirements_content = f.read()

        if "gradio" not in requirements_content and (
            input(
                f"Add gradio=={gr.__version__} to requirements.txt? (y/n) [y]: "
            ).lower()
            != "n"
        ):
            with open(requirements_file, "a", encoding="utf-8") as f:
                f.write(f"gradio=={gr.__version__}\n")
            print(f"Added gradio=={gr.__version__} to requirements.txt")

    print("[bold]Deploying to Google Cloud Run...[/bold]")

    try:
        deploy_command = [
            "gcloud",
            "run",
            "deploy",
            "--source=.",
            "--labels=created-by=gradio",
        ]
        if project_id:
            deploy_command.extend(["--project", project_id])

        subprocess.run(
            deploy_command,
            check=True,
            text=True,
            capture_output=False,
        )
        print("[green]✓ Deployment complete![/green]")
    except subprocess.CalledProcessError as e:
        print(f"[red]Deployment failed: {e}[/red]")
        return
    except KeyboardInterrupt:
        print("\n[yellow]Deployment cancelled.[/yellow]")
        return


def deploy(
    title: Annotated[str | None, Option(help="Spaces app title")] = None,
    app_file: Annotated[
        str | None, Option(help="File containing the Gradio app")
    ] = None,
    provider: Annotated[
        str | None, Option(help="Deployment provider (spaces or gcloud)")
    ] = "spaces",
):
    if provider == "gcloud":
        if app_file and app_file != "app.py":
            print(
                "[yellow]Warning:[/yellow] --app-file is ignored for Google Cloud Run deployment. Using app.py as entry point."
            )
        deploy_to_gcloud()
        return

    if provider != "spaces":
        print(f"[red]Unknown provider: {provider}. Use 'spaces' or 'gcloud'.[/red]")
        return

    if os.getenv("SYSTEM") == "spaces":
        return

    hf_api = huggingface_hub.HfApi()
    whoami = None
    login = False
    try:
        whoami = hf_api.whoami()
        if whoami["auth"]["accessToken"]["role"] != "write":
            login = True
    except OSError:
        login = True
    if login:
        print("Need 'write' access token to create a Spaces repo.")
        huggingface_hub.login(add_to_git_credential=False)
        whoami = hf_api.whoami()

    configuration: None | dict = None
    if os.path.exists(readme_file):
        try:
            configuration = huggingface_hub.metadata_load(readme_file)
        except ValueError:
            pass

    if configuration is None:
        print(
            f"Creating new Spaces Repo in '{repo_directory}'. Collecting metadata, press Enter to accept default value."
        )
        configuration = add_configuration_to_readme(
            title,
            app_file,
        )

    space_id = hf_api.create_repo(
        configuration["title"],
        space_sdk="gradio",
        repo_type="space",
        exist_ok=True,
        space_hardware=configuration.get("hardware"),
    ).repo_id
    hf_api.upload_folder(
        repo_id=space_id,
        repo_type="space",
        folder_path=repo_directory,
    )
    if configuration.get("secrets"):
        for secret_name, secret_value in configuration["secrets"].items():
            hf_api.add_space_secret(space_id, secret_name, secret_value)
    print(f"Space available at https://huggingface.co/spaces/{space_id}")
