#!/usr/bin/env python3
"""
Better Auth Initialization Script

Interactive script to initialize Better Auth configuration.
Supports multiple databases, ORMs, and authentication methods.

.env loading order: process.env > skill/.env > skills/.env > .claude/.env
"""

import os
import sys
import json
import secrets
from pathlib import Path
from typing import Optional, Dict, Any, List
from dataclasses import dataclass


@dataclass
class EnvConfig:
    """Environment configuration holder."""
    secret: str
    url: str
    database_url: Optional[str] = None
    github_client_id: Optional[str] = None
    github_client_secret: Optional[str] = None
    google_client_id: Optional[str] = None
    google_client_secret: Optional[str] = None


class BetterAuthInit:
    """Better Auth configuration initializer."""

    def __init__(self, project_root: Optional[Path] = None):
        """
        Initialize the Better Auth configuration tool.

        Args:
            project_root: Project root directory. Auto-detected if not provided.
        """
        self.project_root = project_root or self._find_project_root()
        self.env_config: Optional[EnvConfig] = None

    @staticmethod
    def _find_project_root() -> Path:
        """
        Find project root by looking for package.json.

        Returns:
            Path to project root.

        Raises:
            RuntimeError: If project root cannot be found.
        """
        current = Path.cwd()
        while current != current.parent:
            if (current / "package.json").exists():
                return current
            current = current.parent

        raise RuntimeError("Could not find project root (no package.json found)")

    def _load_env_files(self) -> Dict[str, str]:
        """
        Load environment variables from .env files in order.

        Loading order: process.env > skill/.env > skills/.env > .claude/.env

        Returns:
            Dictionary of environment variables.
        """
        env_vars = {}

        # Define search paths in reverse priority order
        skill_dir = Path(__file__).parent.parent
        env_paths = [
            self.project_root / ".claude" / ".env",
            self.project_root / ".claude" / "skills" / ".env",
            skill_dir / ".env",
        ]

        # Load from files (lowest priority first)
        for env_path in env_paths:
            if env_path.exists():
                env_vars.update(self._parse_env_file(env_path))

        # Override with process environment (highest priority)
        env_vars.update(os.environ)

        return env_vars

    @staticmethod
    def _parse_env_file(path: Path) -> Dict[str, str]:
        """
        Parse .env file into dictionary.

        Args:
            path: Path to .env file.

        Returns:
            Dictionary of key-value pairs.
        """
        env_vars = {}
        try:
            with open(path, "r") as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith("#") and "=" in line:
                        key, value = line.split("=", 1)
                        # Remove quotes if present
                        value = value.strip().strip('"').strip("'")
                        env_vars[key.strip()] = value
        except Exception as e:
            print(f"Warning: Could not parse {path}: {e}")

        return env_vars

    @staticmethod
    def generate_secret(length: int = 32) -> str:
        """
        Generate cryptographically secure random secret.

        Args:
            length: Length of secret in bytes.

        Returns:
            Hex-encoded secret string.
        """
        return secrets.token_hex(length)

    def prompt_database(self) -> Dict[str, Any]:
        """
        Prompt user for database configuration.

        Returns:
            Database configuration dictionary.
        """
        print("\nDatabase Configuration")
        print("=" * 50)
        print("1. Direct Connection (PostgreSQL/MySQL/SQLite)")
        print("2. Drizzle ORM")
        print("3. Prisma")
        print("4. Kysely")
        print("5. MongoDB")

        choice = input("\nSelect database option (1-5): ").strip()

        db_configs = {
            "1": self._prompt_direct_db,
            "2": self._prompt_drizzle,
            "3": self._prompt_prisma,
            "4": self._prompt_kysely,
            "5": self._prompt_mongodb,
        }

        handler = db_configs.get(choice)
        if not handler:
            print("Invalid choice. Defaulting to direct PostgreSQL.")
            return self._prompt_direct_db()

        return handler()

    def _prompt_direct_db(self) -> Dict[str, Any]:
        """Prompt for direct database connection."""
        print("\nDatabase Type:")
        print("1. PostgreSQL")
        print("2. MySQL")
        print("3. SQLite")

        db_type = input("Select (1-3): ").strip()

        if db_type == "3":
            db_path = input("SQLite file path [./dev.db]: ").strip() or "./dev.db"
            return {
                "type": "sqlite",
                "import": "import Database from 'better-sqlite3';",
                "config": f'database: new Database("{db_path}")'
            }
        elif db_type == "2":
            db_url = input("MySQL connection string: ").strip()
            return {
                "type": "mysql",
                "import": "import { createPool } from 'mysql2/promise';",
                "config": f"database: createPool({{ connectionString: process.env.DATABASE_URL }})",
                "env_var": ("DATABASE_URL", db_url)
            }
        else:
            db_url = input("PostgreSQL connection string: ").strip()
            return {
                "type": "postgresql",
                "import": "import { Pool } from 'pg';",
                "config": "database: new Pool({ connectionString: process.env.DATABASE_URL })",
                "env_var": ("DATABASE_URL", db_url)
            }

    def _prompt_drizzle(self) -> Dict[str, Any]:
        """Prompt for Drizzle ORM configuration."""
        print("\nDrizzle Provider:")
        print("1. PostgreSQL")
        print("2. MySQL")
        print("3. SQLite")

        provider = input("Select (1-3): ").strip()
        provider_map = {"1": "pg", "2": "mysql", "3": "sqlite"}
        provider_name = provider_map.get(provider, "pg")

        return {
            "type": "drizzle",
            "provider": provider_name,
            "import": "import { drizzleAdapter } from 'better-auth/adapters/drizzle';\nimport { db } from '@/db';",
            "config": f"database: drizzleAdapter(db, {{ provider: '{provider_name}' }})"
        }

    def _prompt_prisma(self) -> Dict[str, Any]:
        """Prompt for Prisma configuration."""
        print("\nPrisma Provider:")
        print("1. PostgreSQL")
        print("2. MySQL")
        print("3. SQLite")

        provider = input("Select (1-3): ").strip()
        provider_map = {"1": "postgresql", "2": "mysql", "3": "sqlite"}
        provider_name = provider_map.get(provider, "postgresql")

        return {
            "type": "prisma",
            "provider": provider_name,
            "import": "import { prismaAdapter } from 'better-auth/adapters/prisma';\nimport { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();",
            "config": f"database: prismaAdapter(prisma, {{ provider: '{provider_name}' }})"
        }

    def _prompt_kysely(self) -> Dict[str, Any]:
        """Prompt for Kysely configuration."""
        return {
            "type": "kysely",
            "import": "import { kyselyAdapter } from 'better-auth/adapters/kysely';\nimport { db } from '@/db';",
            "config": "database: kyselyAdapter(db, { provider: 'pg' })"
        }

    def _prompt_mongodb(self) -> Dict[str, Any]:
        """Prompt for MongoDB configuration."""
        mongo_uri = input("MongoDB connection string: ").strip()
        db_name = input("Database name: ").strip()

        return {
            "type": "mongodb",
            "import": "import { mongodbAdapter } from 'better-auth/adapters/mongodb';\nimport { client } from '@/db';",
            "config": f"database: mongodbAdapter(client, {{ databaseName: '{db_name}' }})",
            "env_var": ("MONGODB_URI", mongo_uri)
        }

    def prompt_auth_methods(self) -> List[str]:
        """
        Prompt user for authentication methods.

        Returns:
            List of selected auth method codes.
        """
        print("\nAuthentication Methods")
        print("=" * 50)
        print("Select authentication methods (space-separated, e.g., '1 2 3'):")
        print("1. Email/Password")
        print("2. GitHub OAuth")
        print("3. Google OAuth")
        print("4. Discord OAuth")
        print("5. Two-Factor Authentication (2FA)")
        print("6. Passkeys (WebAuthn)")
        print("7. Magic Link")
        print("8. Username")

        choices = input("\nYour selection: ").strip().split()
        return [c for c in choices if c in "12345678"]

    def generate_auth_config(
        self,
        db_config: Dict[str, Any],
        auth_methods: List[str],
    ) -> str:
        """
        Generate auth.ts configuration file content.

        Args:
            db_config: Database configuration.
            auth_methods: Selected authentication methods.

        Returns:
            Generated TypeScript configuration code.
        """
        imports = ["import { betterAuth } from 'better-auth';"]
        plugins = []
        plugin_imports = []
        config_parts = []

        # Database import
        if db_config.get("import"):
            imports.append(db_config["import"])

        # Email/Password
        if "1" in auth_methods:
            config_parts.append("""  emailAndPassword: {
    enabled: true,
    autoSignIn: true
  }""")

        # OAuth providers
        social_providers = []
        if "2" in auth_methods:
            social_providers.append("""    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }""")

        if "3" in auth_methods:
            social_providers.append("""    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }""")

        if "4" in auth_methods:
            social_providers.append("""    discord: {
      clientId: process.env.DISCORD_CLIENT_ID!,
      clientSecret: process.env.DISCORD_CLIENT_SECRET!,
    }""")

        if social_providers:
            config_parts.append(f"  socialProviders: {{\n{',\\n'.join(social_providers)}\n  }}")

        # Plugins
        if "5" in auth_methods:
            plugin_imports.append("import { twoFactor } from 'better-auth/plugins';")
            plugins.append("twoFactor()")

        if "6" in auth_methods:
            plugin_imports.append("import { passkey } from 'better-auth/plugins';")
            plugins.append("passkey()")

        if "7" in auth_methods:
            plugin_imports.append("import { magicLink } from 'better-auth/plugins';")
            plugins.append("""magicLink({
      sendMagicLink: async ({ email, url }) => {
        // TODO: Implement email sending
        console.log(`Magic link for ${email}: ${url}`);
      }
    })""")

        if "8" in auth_methods:
            plugin_imports.append("import { username } from 'better-auth/plugins';")
            plugins.append("username()")

        # Combine all imports
        all_imports = imports + plugin_imports

        # Build config
        config_body = ",\n".join(config_parts)

        if plugins:
            plugins_str = ",\n    ".join(plugins)
            config_body += f",\n  plugins: [\n    {plugins_str}\n  ]"

        # Final output
        return f"""{chr(10).join(all_imports)}

export const auth = betterAuth({{
  {db_config["config"]},
{config_body}
}});
"""

    def generate_env_file(
        self,
        db_config: Dict[str, Any],
        auth_methods: List[str]
    ) -> str:
        """
        Generate .env file content.

        Args:
            db_config: Database configuration.
            auth_methods: Selected authentication methods.

        Returns:
            Generated .env file content.
        """
        env_vars = [
            f"BETTER_AUTH_SECRET={self.generate_secret()}",
            "BETTER_AUTH_URL=http://localhost:3000",
        ]

        # Database URL
        if db_config.get("env_var"):
            key, value = db_config["env_var"]
            env_vars.append(f"{key}={value}")

        # OAuth credentials
        if "2" in auth_methods:
            env_vars.extend([
                "GITHUB_CLIENT_ID=your_github_client_id",
                "GITHUB_CLIENT_SECRET=your_github_client_secret",
            ])

        if "3" in auth_methods:
            env_vars.extend([
                "GOOGLE_CLIENT_ID=your_google_client_id",
                "GOOGLE_CLIENT_SECRET=your_google_client_secret",
            ])

        if "4" in auth_methods:
            env_vars.extend([
                "DISCORD_CLIENT_ID=your_discord_client_id",
                "DISCORD_CLIENT_SECRET=your_discord_client_secret",
            ])

        return "\n".join(env_vars) + "\n"

    def run(self) -> None:
        """Run interactive initialization."""
        print("=" * 50)
        print("Better Auth Configuration Generator")
        print("=" * 50)

        # Load existing env
        env_vars = self._load_env_files()

        # Prompt for configuration
        db_config = self.prompt_database()
        auth_methods = self.prompt_auth_methods()

        # Generate files
        auth_config = self.generate_auth_config(db_config, auth_methods)
        env_content = self.generate_env_file(db_config, auth_methods)

        # Display output
        print("\n" + "=" * 50)
        print("Generated Configuration")
        print("=" * 50)

        print("\n--- auth.ts ---")
        print(auth_config)

        print("\n--- .env ---")
        print(env_content)

        # Offer to save
        save = input("\nSave configuration files? (y/N): ").strip().lower()
        if save == "y":
            self._save_files(auth_config, env_content)
        else:
            print("Configuration not saved.")

    def _save_files(self, auth_config: str, env_content: str) -> None:
        """
        Save generated configuration files.

        Args:
            auth_config: auth.ts content.
            env_content: .env content.
        """
        # Save auth.ts
        auth_locations = [
            self.project_root / "lib" / "auth.ts",
            self.project_root / "src" / "lib" / "auth.ts",
            self.project_root / "utils" / "auth.ts",
            self.project_root / "auth.ts",
        ]

        print("\nWhere to save auth.ts?")
        for i, loc in enumerate(auth_locations, 1):
            print(f"{i}. {loc}")
        print("5. Custom path")

        choice = input("Select (1-5): ").strip()
        if choice == "5":
            custom_path = input("Enter path: ").strip()
            auth_path = Path(custom_path)
        else:
            idx = int(choice) - 1 if choice.isdigit() else 0
            auth_path = auth_locations[idx]

        auth_path.parent.mkdir(parents=True, exist_ok=True)
        auth_path.write_text(auth_config)
        print(f"Saved: {auth_path}")

        # Save .env
        env_path = self.project_root / ".env"
        if env_path.exists():
            backup = self.project_root / ".env.backup"
            env_path.rename(backup)
            print(f"Backed up existing .env to {backup}")

        env_path.write_text(env_content)
        print(f"Saved: {env_path}")

        print("\nNext steps:")
        print("1. Run: npx @better-auth/cli generate")
        print("2. Apply database migrations")
        print("3. Mount API handler in your framework")
        print("4. Create client instance")


def main() -> int:
    """
    Main entry point.

    Returns:
        Exit code (0 for success, 1 for error).
    """
    try:
        initializer = BetterAuthInit()
        initializer.run()
        return 0
    except KeyboardInterrupt:
        print("\n\nOperation cancelled.")
        return 1
    except Exception as e:
        print(f"\nError: {e}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())
