"""
Provider Connector - Interactive LLM provider configuration.

Handles API key input, validation, and .env file management
for connecting to various LLM providers.
"""

import os
from getpass import getpass
from pathlib import Path
from typing import Any

from dotenv import load_dotenv
from rich.console import Console
from rich.panel import Panel
from rich.table import Table

from groknroll.connect.providers import (
    ProviderDefinition,
    get_provider,
    list_providers,
)


class ProviderConnector:
    """
    Manages LLM provider connections and credentials.

    Provides interactive prompts for API key entry, validates
    credentials by making test API calls, and saves configuration
    to .env file.

    Example:
        connector = ProviderConnector()

        # Interactive provider selection
        provider = connector.select_provider()

        # Configure specific provider
        success = connector.configure_provider("openai")

        # List configured providers
        configured = connector.get_configured_providers()
    """

    def __init__(
        self,
        env_path: Path | None = None,
        console: Console | None = None,
    ):
        """
        Initialize ProviderConnector.

        Args:
            env_path: Path to .env file (default: current dir)
            console: Rich console for output
        """
        self.env_path = env_path or Path.cwd() / ".env"
        self.console = console or Console()

        # Load .env file if it exists to populate environment
        if self.env_path.exists():
            load_dotenv(self.env_path, override=False)

    def select_provider(self) -> str | None:
        """
        Display interactive provider picker.

        Returns:
            Selected provider name or None if cancelled
        """
        providers = list_providers()

        # Build the picker display
        self.console.print()
        panel_content = []
        for i, provider in enumerate(providers, 1):
            status = self._get_provider_status(provider.name)
            status_icon = "[green]✓[/green]" if status else "[dim]○[/dim]"
            models_str = ", ".join(provider.models[:2])
            if len(provider.models) > 2:
                models_str += ", ..."
            panel_content.append(
                f"  {status_icon} {i}. [cyan]{provider.name:<12}[/cyan] ({models_str})"
            )

        self.console.print(
            Panel(
                "\n".join(panel_content),
                title="Select LLM Provider",
                border_style="blue",
            )
        )

        # Get user selection
        try:
            selection = input("\nEnter number (or 'q' to cancel): ").strip()
            if selection.lower() in ("q", "quit", "cancel", ""):
                return None

            idx = int(selection) - 1
            if 0 <= idx < len(providers):
                return providers[idx].name
            else:
                self.console.print("[red]Invalid selection[/red]")
                return None
        except (ValueError, EOFError, KeyboardInterrupt):
            return None

    def configure_provider(self, provider_name: str) -> bool:
        """
        Configure a specific provider interactively.

        Args:
            provider_name: Name of provider to configure

        Returns:
            True if configuration succeeded
        """
        provider = get_provider(provider_name)
        if not provider:
            self.console.print(f"[red]Unknown provider: {provider_name}[/red]")
            return False

        self.console.print(f"\n[bold]Configuring {provider.display_name}[/bold]")
        self.console.print(f"[dim]{provider.description}[/dim]\n")

        # Collect all required credentials
        credentials: dict[str, str] = {}

        # Special handling for local LLM
        if provider_name == "local":
            return self._configure_local_provider(provider)

        # Main API key
        for env_var in provider.env_vars:
            existing = os.getenv(env_var, "")
            if existing:
                masked = self._mask_key(existing)
                self.console.print(f"[dim]Current {env_var}: {masked}[/dim]")
                overwrite = input("Overwrite? [y/N]: ").strip().lower()
                if overwrite != "y":
                    credentials[env_var] = existing
                    continue

            try:
                value = getpass(f"Enter {env_var}: ")
                if not value:
                    self.console.print("[yellow]Cancelled[/yellow]")
                    return False
                credentials[env_var] = value
            except (EOFError, KeyboardInterrupt):
                self.console.print("\n[yellow]Cancelled[/yellow]")
                return False

        # Extra vars (like Azure endpoint)
        for env_var in provider.extra_vars:
            existing = os.getenv(env_var, "")
            if existing:
                self.console.print(f"[dim]Current {env_var}: {existing}[/dim]")
                overwrite = input("Overwrite? [y/N]: ").strip().lower()
                if overwrite != "y":
                    credentials[env_var] = existing
                    continue

            try:
                value = input(f"Enter {env_var}: ").strip()
                if not value:
                    self.console.print("[yellow]Cancelled[/yellow]")
                    return False
                credentials[env_var] = value
            except (EOFError, KeyboardInterrupt):
                self.console.print("\n[yellow]Cancelled[/yellow]")
                return False

        # Validate credentials
        self.console.print("\n[dim]Testing connection...[/dim]")
        valid, error = self.validate_credentials(provider_name, credentials)

        if not valid:
            self.console.print(f"[red]Connection failed: {error}[/red]")
            save_anyway = input("Save anyway? [y/N]: ").strip().lower()
            if save_anyway != "y":
                return False

        # Save to .env
        for key, value in credentials.items():
            self.save_to_env(key, value)

        # Also set in current environment
        for key, value in credentials.items():
            os.environ[key] = value

        if valid:
            self.console.print(f"[green]✓ Connected to {provider.display_name}[/green]")
        else:
            self.console.print(f"[yellow]⚠ Saved credentials for {provider.display_name}[/yellow]")

        self.console.print(f"[dim]Saved to {self.env_path}[/dim]")
        return True

    def validate_credentials(
        self,
        provider_name: str,
        credentials: dict[str, str],
    ) -> tuple[bool, str]:
        """
        Validate provider credentials by making a test API call.

        Args:
            provider_name: Provider name
            credentials: Dict of env_var -> value

        Returns:
            Tuple of (success, error_message)
        """
        provider = get_provider(provider_name)
        if not provider:
            return False, f"Unknown provider: {provider_name}"

        try:
            if provider_name == "openai":
                return self._validate_openai(credentials, provider)
            elif provider_name == "anthropic":
                return self._validate_anthropic(credentials, provider)
            elif provider_name == "gemini":
                return self._validate_gemini(credentials, provider)
            elif provider_name == "xai":
                return self._validate_xai(credentials, provider)
            elif provider_name == "groq":
                return self._validate_groq(credentials, provider)
            elif provider_name == "openrouter":
                return self._validate_openrouter(credentials, provider)
            elif provider_name == "azure":
                return self._validate_azure(credentials, provider)
            elif provider_name == "local":
                return self._validate_local(credentials)
            else:
                # For unknown providers, just check key format
                if not provider.env_vars:
                    return True, ""  # No API key required
                api_key = credentials.get(provider.env_vars[0], "")
                if len(api_key) > 10:
                    return True, ""
                return False, "API key seems too short"
        except Exception as e:
            return False, str(e)

    def _validate_openai(
        self, credentials: dict[str, str], provider: ProviderDefinition
    ) -> tuple[bool, str]:
        """Validate OpenAI credentials"""
        try:
            import openai

            api_key = credentials.get("OPENAI_API_KEY", "")
            client = openai.OpenAI(api_key=api_key)
            # Simple models list call to validate
            client.models.list()
            return True, ""
        except ImportError:
            return False, "openai package not installed"
        except Exception as e:
            return False, str(e)

    def _validate_anthropic(
        self, credentials: dict[str, str], provider: ProviderDefinition
    ) -> tuple[bool, str]:
        """Validate Anthropic credentials"""
        try:
            import anthropic

            api_key = credentials.get("ANTHROPIC_API_KEY", "")
            client = anthropic.Anthropic(api_key=api_key)
            # Make a minimal request
            client.messages.create(
                model=provider.test_model,
                max_tokens=1,
                messages=[{"role": "user", "content": "hi"}],
            )
            return True, ""
        except ImportError:
            return False, "anthropic package not installed"
        except Exception as e:
            return False, str(e)

    def _validate_gemini(
        self, credentials: dict[str, str], provider: ProviderDefinition
    ) -> tuple[bool, str]:
        """Validate Google Gemini credentials"""
        try:
            import google.generativeai as genai

            api_key = credentials.get("GEMINI_API_KEY", "")
            genai.configure(api_key=api_key)
            model = genai.GenerativeModel(provider.test_model)
            model.generate_content("hi", generation_config={"max_output_tokens": 1})
            return True, ""
        except ImportError:
            return False, "google-generativeai package not installed"
        except Exception as e:
            return False, str(e)

    def _validate_xai(
        self, credentials: dict[str, str], provider: ProviderDefinition
    ) -> tuple[bool, str]:
        """Validate xAI credentials using OpenAI-compatible API"""
        try:
            import openai

            api_key = credentials.get("XAI_API_KEY", "")
            client = openai.OpenAI(
                api_key=api_key,
                base_url="https://api.x.ai/v1",
            )
            client.chat.completions.create(
                model=provider.test_model,
                messages=[{"role": "user", "content": "hi"}],
                max_tokens=1,
            )
            return True, ""
        except ImportError:
            return False, "openai package not installed"
        except Exception as e:
            return False, str(e)

    def _validate_groq(
        self, credentials: dict[str, str], provider: ProviderDefinition
    ) -> tuple[bool, str]:
        """Validate Groq credentials using OpenAI-compatible API"""
        try:
            import openai

            api_key = credentials.get("GROQ_API_KEY", "")
            client = openai.OpenAI(
                api_key=api_key,
                base_url="https://api.groq.com/openai/v1",
            )
            client.chat.completions.create(
                model=provider.test_model,
                messages=[{"role": "user", "content": "hi"}],
                max_tokens=1,
            )
            return True, ""
        except ImportError:
            return False, "openai package not installed"
        except Exception as e:
            return False, str(e)

    def _validate_openrouter(
        self, credentials: dict[str, str], provider: ProviderDefinition
    ) -> tuple[bool, str]:
        """Validate OpenRouter credentials"""
        try:
            import openai

            api_key = credentials.get("OPENROUTER_API_KEY", "")
            client = openai.OpenAI(
                api_key=api_key,
                base_url="https://openrouter.ai/api/v1",
            )
            client.chat.completions.create(
                model=provider.test_model,
                messages=[{"role": "user", "content": "hi"}],
                max_tokens=1,
            )
            return True, ""
        except ImportError:
            return False, "openai package not installed"
        except Exception as e:
            return False, str(e)

    def _validate_azure(
        self, credentials: dict[str, str], provider: ProviderDefinition
    ) -> tuple[bool, str]:
        """Validate Azure OpenAI credentials"""
        try:
            import openai

            api_key = credentials.get("AZURE_OPENAI_API_KEY", "")
            endpoint = credentials.get("AZURE_OPENAI_ENDPOINT", "")

            if not endpoint:
                return False, "Azure endpoint required"

            client = openai.AzureOpenAI(
                api_key=api_key,
                azure_endpoint=endpoint,
                api_version="2024-02-15-preview",
            )
            # Try to list deployments
            client.models.list()
            return True, ""
        except ImportError:
            return False, "openai package not installed"
        except Exception as e:
            return False, str(e)

    def _configure_local_provider(self, provider: ProviderDefinition) -> bool:
        """Configure local OpenAI-compatible LLM server with presets"""

        # Check for existing config in .env
        existing_endpoint = os.getenv("LOCAL_LLM_ENDPOINT", "")
        existing_model = os.getenv("LOCAL_LLM_MODEL", "")

        if existing_endpoint and existing_model:
            self.console.print(f"\n[bold]Existing Local LLM Config:[/bold]")
            self.console.print(f"  Endpoint: [cyan]{existing_endpoint}[/cyan]")
            self.console.print(f"  Model:    [cyan]{existing_model}[/cyan]")

            # Test existing config
            self.console.print("\n[dim]Testing connection...[/dim]")
            credentials = {
                "LOCAL_LLM_ENDPOINT": existing_endpoint,
                "LOCAL_LLM_MODEL": existing_model,
            }
            existing_key = os.getenv("LOCAL_LLM_API_KEY", "")
            if existing_key:
                credentials["LOCAL_LLM_API_KEY"] = existing_key

            valid, error = self._validate_local(credentials)

            if valid:
                self.console.print(f"[green]✓ Connected to {existing_model}[/green]")
                try:
                    use_existing = input("\nUse this config? [Y/n]: ").strip().lower()
                    if use_existing != "n":
                        return True
                except (EOFError, KeyboardInterrupt):
                    return False
            else:
                self.console.print(f"[yellow]Connection failed: {error}[/yellow]")
                try:
                    reconfigure = input("Reconfigure? [Y/n]: ").strip().lower()
                    if reconfigure == "n":
                        return False
                except (EOFError, KeyboardInterrupt):
                    return False

        # Preloaded configurations for common local LLM servers
        LOCAL_PRESETS = {
            "1": {
                "name": "LM Studio",
                "endpoint": "http://localhost:1234/v1",
                "default_model": "local-model",
                "needs_auth": False,
                "desc": "localhost:1234",
            },
            "2": {
                "name": "Ollama",
                "endpoint": "http://localhost:11434/v1",
                "default_model": "llama3.2",
                "needs_auth": False,
                "desc": "localhost:11434",
            },
            "3": {
                "name": "vLLM",
                "endpoint": "http://localhost:8000/v1",
                "default_model": "default",
                "needs_auth": False,
                "desc": "localhost:8000",
            },
            "4": {
                "name": "LocalAI",
                "endpoint": "http://localhost:8080/v1",
                "default_model": "gpt-4",
                "needs_auth": False,
                "desc": "localhost:8080",
            },
            "5": {
                "name": "Olares",
                "endpoint": "",  # User must provide
                "default_model": "",
                "needs_auth": True,
                "desc": "cloud (requires URL + token)",
            },
            "6": {
                "name": "Custom",
                "endpoint": "",
                "default_model": "",
                "needs_auth": False,
                "desc": "manual entry",
            },
        }

        # Display preset menu
        self.console.print("\n[bold]Select Local LLM Server:[/bold]")
        for key, preset in LOCAL_PRESETS.items():
            self.console.print(f"  [cyan]{key}.[/cyan] {preset['name']:<10} [dim]{preset['desc']}[/dim]")

        # Get selection
        try:
            selection = input("\nEnter number (or 'q' to cancel): ").strip()
            if selection.lower() in ("q", "quit", "cancel", ""):
                return False

            if selection not in LOCAL_PRESETS:
                self.console.print("[red]Invalid selection[/red]")
                return False

            preset = LOCAL_PRESETS[selection]
        except (EOFError, KeyboardInterrupt):
            self.console.print("\n[yellow]Cancelled[/yellow]")
            return False

        self.console.print(f"\n[bold]Configuring {preset['name']}[/bold]")

        credentials: dict[str, str] = {}

        # Get endpoint - use preset directly for known servers
        if preset["endpoint"]:
            endpoint = preset["endpoint"]
            self.console.print(f"[dim]Endpoint: {endpoint}[/dim]")
        else:
            # Must enter endpoint (Olares/Custom)
            existing = os.getenv("LOCAL_LLM_ENDPOINT", "")
            prompt = f"Enter endpoint URL"
            if existing:
                prompt += f" [{existing}]"
            prompt += ": "
            try:
                endpoint = input(prompt).strip()
                if not endpoint:
                    endpoint = existing
                if not endpoint:
                    self.console.print("[red]Endpoint required[/red]")
                    return False
            except (EOFError, KeyboardInterrupt):
                self.console.print("\n[yellow]Cancelled[/yellow]")
                return False

        credentials["LOCAL_LLM_ENDPOINT"] = endpoint

        # Try to auto-detect models
        self.console.print("[dim]Detecting models...[/dim]")
        models = self._detect_local_models(endpoint, credentials.get("LOCAL_LLM_API_KEY"))

        if models:
            # Auto-select first model, no prompting needed
            model = models[0]
            self.console.print(f"[green]✓ Found {len(models)} model(s), using: {model}[/green]")
            if len(models) > 1:
                other_models = ", ".join(models[1:5])
                if len(models) > 5:
                    other_models += f", ... (+{len(models) - 5} more)"
                self.console.print(f"[dim]  Also available: {other_models}[/dim]")
        elif preset["default_model"]:
            # Use preset's default model without prompting
            model = preset["default_model"]
            self.console.print(f"[yellow]Server not reachable, using default: {model}[/yellow]")
        else:
            # Only prompt for Custom/Olares when no default
            existing_model = os.getenv("LOCAL_LLM_MODEL", "")
            default_model = existing_model or "local-model"
            try:
                model = input(f"Enter model name [{default_model}]: ").strip()
                if not model:
                    model = default_model
            except (EOFError, KeyboardInterrupt):
                self.console.print("\n[yellow]Cancelled[/yellow]")
                return False

        credentials["LOCAL_LLM_MODEL"] = model

        # API key (required for some servers like Olares)
        if preset["needs_auth"]:
            self.console.print("\n[yellow]Authentication required[/yellow]")
            self.console.print("[dim]Get your API token from your server dashboard[/dim]")
            try:
                api_key = input("Enter API key/token: ").strip()
                if not api_key:
                    self.console.print("[red]API key required for this server[/red]")
                    return False
                credentials["LOCAL_LLM_API_KEY"] = api_key
            except (EOFError, KeyboardInterrupt):
                self.console.print("\n[yellow]Cancelled[/yellow]")
                return False
        else:
            # Keep existing API key if set (no prompting for local servers)
            existing_key = os.getenv("LOCAL_LLM_API_KEY", "")
            if existing_key:
                credentials["LOCAL_LLM_API_KEY"] = existing_key

        # Validate connection
        self.console.print("\n[dim]Testing connection...[/dim]")
        valid, error = self._validate_local(credentials)

        if not valid:
            self.console.print(f"[red]Connection failed: {error}[/red]")
            save_anyway = input("Save anyway? [y/N]: ").strip().lower()
            if save_anyway != "y":
                return False

        # Save to .env
        for key, value in credentials.items():
            self.save_to_env(key, value)
            os.environ[key] = value

        if valid:
            self.console.print(f"[green]✓ Connected to {preset['name']} ({model})[/green]")
        else:
            self.console.print(f"[yellow]⚠ Saved {preset['name']} configuration[/yellow]")

        self.console.print(f"[dim]Saved to {self.env_path}[/dim]")
        return True

    def _detect_local_models(self, endpoint: str, api_key: str | None = None) -> list[str]:
        """Try to auto-detect available models from local LLM server"""
        try:
            import openai

            client = openai.OpenAI(
                api_key=api_key or "not-needed",
                base_url=endpoint,
                timeout=5.0,
            )

            models_response = client.models.list()
            models = [m.id for m in models_response.data]
            return sorted(models)
        except Exception:
            return []

    def _validate_local(self, credentials: dict[str, str]) -> tuple[bool, str]:
        """Validate local OpenAI-compatible LLM connection"""
        try:
            import openai

            endpoint = credentials.get("LOCAL_LLM_ENDPOINT", "")
            model = credentials.get("LOCAL_LLM_MODEL", "local-model")
            api_key = credentials.get("LOCAL_LLM_API_KEY", "not-needed")

            if not endpoint:
                return False, "Endpoint required"

            client = openai.OpenAI(
                api_key=api_key,
                base_url=endpoint,
            )

            # Try a simple completion
            client.chat.completions.create(
                model=model,
                messages=[{"role": "user", "content": "hi"}],
                max_tokens=1,
            )
            return True, ""
        except ImportError:
            return False, "openai package not installed"
        except Exception as e:
            error_str = str(e)
            # Detect OAuth redirect responses (common with Olares, etc.)
            if "auth" in error_str.lower() and (
                "redirect" in error_str.lower() or "href=" in error_str.lower()
            ):
                return (
                    False,
                    "Server requires OAuth authentication. Get an API token from your server's dashboard.",
                )
            # Detect 401/403 auth errors
            if "401" in error_str or "403" in error_str or "unauthorized" in error_str.lower():
                return False, "Authentication failed. Check your API key."
            return False, error_str

    def save_to_env(self, key: str, value: str) -> None:
        """
        Add or update key in .env file.

        Args:
            key: Environment variable name
            value: Value to set
        """
        lines: list[str] = []
        found = False

        if self.env_path.exists():
            content = self.env_path.read_text()
            for line in content.splitlines():
                # Handle lines with and without values
                if line.startswith(f"{key}="):
                    lines.append(f"{key}={value}")
                    found = True
                else:
                    lines.append(line)

        if not found:
            lines.append(f"{key}={value}")

        # Ensure trailing newline
        self.env_path.write_text("\n".join(lines) + "\n")

    def get_configured_providers(self) -> list[dict[str, Any]]:
        """
        Get list of configured providers with their status.

        Returns:
            List of dicts with provider info and status
        """
        result = []
        for provider in list_providers():
            status = self._get_provider_status(provider.name)
            result.append(
                {
                    "name": provider.name,
                    "display_name": provider.display_name,
                    "description": provider.description,
                    "configured": status,
                    "models": provider.models,
                }
            )
        return result

    def _get_provider_status(self, provider_name: str) -> bool:
        """Check if provider has credentials configured"""
        provider = get_provider(provider_name)
        if not provider:
            return False

        # Check all required env vars
        for env_var in provider.env_vars:
            if not os.getenv(env_var):
                return False

        # Check extra vars if any
        for env_var in provider.extra_vars:
            if not os.getenv(env_var):
                return False

        return True

    def _mask_key(self, key: str) -> str:
        """Mask API key for display"""
        if len(key) <= 8:
            return "*" * len(key)
        return key[:4] + "*" * (len(key) - 8) + key[-4:]

    def show_providers(self) -> None:
        """Display configured providers in a table"""
        table = Table(title="LLM Providers", show_header=True, header_style="bold")
        table.add_column("Provider", style="cyan")
        table.add_column("Status")
        table.add_column("Description", style="dim")

        for provider in list_providers():
            status = self._get_provider_status(provider.name)
            status_text = "[green]Configured[/green]" if status else "[dim]Not configured[/dim]"
            table.add_row(provider.display_name, status_text, provider.description)

        self.console.print(table)


def connect_provider(provider_name: str | None = None) -> bool:
    """
    Convenience function to connect a provider.

    Args:
        provider_name: Optional provider name. If None, shows picker.

    Returns:
        True if connection succeeded
    """
    connector = ProviderConnector()

    if provider_name:
        return connector.configure_provider(provider_name)
    else:
        selected = connector.select_provider()
        if selected:
            return connector.configure_provider(selected)
        return False


def show_providers() -> None:
    """Convenience function to show configured providers"""
    connector = ProviderConnector()
    connector.show_providers()
