import importlib.resources
import os
import subprocess
import sys
from enum import Enum
from pathlib import Path
from subprocess import DEVNULL

import requests
import typer
from dotenv import load_dotenv

load_dotenv(override=True)

app = typer.Typer(
    help="AI Commit Message Generator. Reads staged Git diff and suggests a message"
)

SESSION = requests.Session()

DEFAULT_DEEPSEEK_MODEL = "deepseek-reasoner"
DEFAULT_GEMINI_MODEL = "gemini-1.5-flash"

HOOK_SCRIPT_CONTENT = """#!/usr/bin/env bash
COMMIT_MSG_FILE=$1
echo "--- Prepare-Commit-Message Hook Triggered ---" > /dev/tty
cd "$(git rev-parse --show-toplevel)" || exit 1
EXISTING_MSG=$(grep -v '^#' "$COMMIT_MSG_FILE" | head -n 1 | tr -d '[:space:]')
if [[ -n "$EXISTING_MSG" ]]; then
    echo "INFO: User provided an existing message. Skipping AI generation." > /dev/tty
    exit 0
fi
set -e
GENERATED_MSG=$(aicommitter generate)
set +e
if [[ -z "$(echo "$GENERATED_MSG" | tr -d '[:space:]')" ]]; then
    echo "ERROR: Generated message is empty. Manual edit required." > /dev/tty
    exit 1
fi
echo "$GENERATED_MSG" > "$COMMIT_MSG_FILE"
echo "INFO: Successfully generated and set the commit message." > /dev/tty
echo "--- Generated Message ---" > /dev/tty
echo "$GENERATED_MSG" > /dev/tty
echo "-------------------------" > /dev/tty
exit 0
"""


class AIProvider(str, Enum):
    DEEPSEEK = "deepseek"
    GEMINI = "gemini"


def get_readme_content(filepath: str = "docs.md") -> str:
    try:
        return importlib.resources.read_text("aicommitter", filepath)
    except FileNotFoundError:
        return typer.style(
            f"Error: Documentation file '{filepath}' not found.",
            fg=typer.colors.RED,
        )
    except Exception as e:
        return typer.style(f"Error reading docs: {e}", fg=typer.colors.RED)


@app.command(name="docs")
def show_docs():
    readme_content = get_readme_content()
    typer.echo(typer.style(f"\n{readme_content}", fg=typer.colors.GREEN, italic=True))


@app.command(name="install")
def install_hook():
    """Installs the prepare-commit-msg hook in the current Git repository."""
    try:
        git_dir = subprocess.run(
            ["git", "rev-parse", "--git-dir"],
            capture_output=True,
            check=True,
            text=True,
        ).stdout.strip()

        hook_path = os.path.join(git_dir, "hooks", "prepare-commit-msg")
        with open(hook_path, "w") as f:
            f.write(HOOK_SCRIPT_CONTENT)
        os.chmod(hook_path, 0o755)

        typer.echo(
            typer.style(
                f"\nSuccessfully installed hook in {hook_path}",
                fg=typer.colors.GREEN,
                bold=True,
            )
        )
        typer.echo("Run 'git commit' in this repository to test.")

    except subprocess.CalledProcessError:
        typer.echo(
            typer.style("ERROR: Not in a Git repository.", fg=typer.colors.RED),
            err=True,
        )
        raise typer.Exit(code=1)


def get_diff() -> str:
    """Runs 'git diff --cached' and handles errors."""
    try:
        subprocess.run(
            ["git", "rev-parse", "--is-inside-work-tree"],
            stdout=DEVNULL,
            stderr=DEVNULL,
            check=True,
        )
        diff = subprocess.run(
            ["git", "diff", "--cached"],
            capture_output=True,
            text=True,
            check=True,
        )
        return diff.stdout
    except subprocess.CalledProcessError:
        typer.echo("Error: Git command failed. Are you in a Git repository?", err=True)
        return ""
    except FileNotFoundError:
        typer.echo("Error: 'git' command not found.", err=True)
        return ""


def call_deepseek(diff: str, api_key: str, model: str) -> str:
    url = "https://api.deepseek.com/chat/completions"
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}",
    }
    prompt = f"""
    You are a helpful assistant that writes concise Git commit messages.
    Write a commit message that describes the following diff, following Conventional Commit format.
    Diff:
    {diff}
    """
    data = {
        "model": model,
        "messages": [{"role": "system", "content": prompt}],
        "stream": False,
    }
    try:
        response = SESSION.post(url, headers=headers, json=data, timeout=120)
        response.raise_for_status()
        return (
            response.json()
            .get("choices", [{}])[0]
            .get("message", {})
            .get("content", "")
            .strip()
        )
    except requests.exceptions.Timeout:
        return "Error: Request timed out after 120 seconds"
    except requests.exceptions.HTTPError as e:
        return f"Error: HTTP {e.response.status_code}"
    except requests.exceptions.RequestException as e:
        return f"Error: {str(e)}"


def call_gemini(diff: str, api_key: str, model: str) -> str:
    # Google Generative AI REST API Endpoint
    url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={api_key}"

    headers = {"Content-Type": "application/json"}

    prompt = f"""
    You are a helpful assistant that writes concise Git commit messages.
    Write a commit message that describes the following diff, following Conventional Commit format.
    Do not include markdown code blocks (like ```) in the output, just the raw message.

    Diff:
    {diff}
    """

    # Gemini payload structure
    data = {"contents": [{"parts": [{"text": prompt}]}]}

    response = SESSION.post(url, headers=headers, json=data, timeout=120)

    # Detailed error handling for Google API
    if response.status_code != 200:
        typer.echo(f"Gemini API Error: {response.text}", err=True)
        response.raise_for_status()

    result = response.json()
    try:
        # Navigate Gemini's JSON response structure
        return result["candidates"][0]["content"]["parts"][0]["text"].strip()
    except (KeyError, IndexError):
        typer.echo(f"Error parsing Gemini response: {result}", err=True)
        return ""


# Generate the commit message
def generate_message(
    diff: str, provider: AIProvider, api_key: str, model_name: str
) -> str:
    """Dispatches the generation request to the correct provider"""

    try:
        typer.echo(
            f"... Calling {provider.value.title()} ({model_name}) for generation ..."
        )

        if provider == AIProvider.DEEPSEEK:
            return call_deepseek(diff, api_key, model_name)
        elif provider == AIProvider.GEMINI:
            return call_gemini(diff, api_key, model_name)

    except requests.exceptions.RequestException as e:
        typer.echo(f"Error: API request failed. {e}", err=True)
        return ""
    return ""


# Entry point for application
@app.command(name="generate")
def cli_generate(
    commit: bool = typer.Option(
        False,
        "--commit",
        "-c",
        help="Immediately commit the suggested message if confirmed.",
    ),
    provider: str = typer.Option(
        None, "--provider", "-p", help="Explicitly choose 'deepseek' or 'gemini'."
    ),
    model: str = typer.Option(
        None, "--model", "-m", help="Override the default model name."
    ),
):
    """
    Generates a Conventional Commit message.
    Automatically detects DEEPSEEK_API_KEY or GEMINI_API_KEY.
    """

    diff = get_diff()
    if not diff.strip():
        typer.echo("INFO: No staged changes found.")
        raise typer.Exit(code=0)

    # --- [NEW] Provider and Key Resolution Logic ---
    deepseek_key = os.getenv("DEEPSEEK_API_KEY")
    gemini_key = os.getenv("GEMINI_API_KEY")

    selected_provider = None
    selected_key = None
    selected_model = model  # User override or default

    # Logic 1: User explicitly asked for a provider
    if provider:
        if provider.lower() == "deepseek":
            if not deepseek_key:
                typer.echo(
                    "Error: --provider is deepseek but DEEPSEEK_API_KEY is not set.",
                    err=True,
                )
                raise typer.Exit(1)
            selected_provider = AIProvider.DEEPSEEK
            selected_key = deepseek_key
            if not selected_model:
                selected_model = DEFAULT_DEEPSEEK_MODEL

        elif provider.lower() == "gemini":
            if not gemini_key:
                typer.echo(
                    "Error: --provider is gemini but GEMINI_API_KEY is not set.",
                    err=True,
                )
                raise typer.Exit(1)
            selected_provider = AIProvider.GEMINI
            selected_key = gemini_key
            if not selected_model:
                selected_model = DEFAULT_GEMINI_MODEL
        else:
            typer.echo(
                f"Error: Unknown provider '{provider}'. Use 'deepseek' or 'gemini'.",
                err=True,
            )
            raise typer.Exit(1)

    # Logic 2: Auto-detect based on env vars
    else:
        if deepseek_key:
            selected_provider = AIProvider.DEEPSEEK
            selected_key = deepseek_key
            if not selected_model:
                selected_model = DEFAULT_DEEPSEEK_MODEL
            # If both exist, we default to DeepSeek unless specific logic changes
            if gemini_key:
                typer.echo(
                    "Info: Both keys found. Defaulting to DeepSeek. Use --provider gemini to switch."
                )

        elif gemini_key:
            selected_provider = AIProvider.GEMINI
            selected_key = gemini_key
            if not selected_model:
                selected_model = DEFAULT_GEMINI_MODEL

        else:
            typer.echo(
                "Error: No API keys found. Please export DEEPSEEK_API_KEY or GEMINI_API_KEY.",
                err=True,
            )
            raise typer.Exit(1)

    # --- Execute Generation ---
    commit_message = generate_message(
        diff, selected_provider, selected_key, selected_model
    )

    if not commit_message:
        raise typer.Exit(code=1)

    typer.echo("\n" + "=" * 50)
    typer.echo(f"Suggested Commit Message ({selected_provider.value}):")
    typer.echo(commit_message)
    typer.echo("=" * 50 + "\n")

    if commit:
        confirm = typer.confirm("Do you want to use this message to commit?")
        if confirm:
            try:
                subprocess.run(["git", "commit", "-m", commit_message], check=True)
                typer.echo(
                    typer.style("Commit successful!", fg=typer.colors.GREEN, bold=True)
                )
            except subprocess.CalledProcessError:
                typer.echo("Error: Git commit failed.", err=True)
                raise typer.Exit(code=1)
        else:
            typer.echo("Commit aborted by user.")
            raise typer.Exit()
    else:
        typer.echo("Message generated. Run with '-c' to commit automatically.")


if __name__ == "__main__":
    try:
        app()
    finally:
        SESSION.close()
