"""
Main application class for Socrates AI - Command-Based CLI Interface
"""

import datetime
import getpass
import os
import signal
import subprocess
import sys
import time
import webbrowser
from pathlib import Path
from typing import Any

from colorama import Fore, Style

from socratic_system.models import ProjectContext, User
from socratic_system.orchestration import AgentOrchestrator
from socratic_system.ui.command_handler import CommandHandler
from socratic_system.ui.commands import (  # Analytics commands; Code commands; Finalize commands; Collaboration commands; Skills commands; Session commands; Conversation commands; Debug commands; Document commands; System commands; Note commands; Project commands; Statistics commands; User commands; Subscription commands; GitHub commands
    AdvanceCommand,
    AnalyticsAnalyzeCommand,
    AnalyticsBreakdownCommand,
    AnalyticsRecommendCommand,
    AnalyticsStatusCommand,
    AnalyticsSummaryCommand,
    AnalyticsTrendsCommand,
    BackCommand,
    ChatCommand,
    ClearCommand,
    CodeDocsCommand,
    CodeGenerateCommand,
    CollabAddCommand,
    CollabListCommand,
    CollabRemoveCommand,
    CollabRoleCommand,
    ConvSearchCommand,
    ConvSummaryCommand,
    DebugCommand,
    DocImportCommand,
    DocImportDirCommand,
    DocImportUrlCommand,
    DocListCommand,
    DocPasteCommand,
    DocsCommand,
    DoneCommand,
    ExitCommand,
    ExplainCommand,
    FileDeleteCommand,
    FinalizeDocsCommand,
    FinalizeGenerateCommand,
    GithubImportCommand,
    GithubPullCommand,
    GithubPushCommand,
    GithubSyncCommand,
    HelpCommand,
    HintCommand,
    InfoCommand,
    LLMCommand,
    LogsCommand,
    MaturityCommand,
    MaturityHistoryCommand,
    MaturityStatusCommand,
    MaturitySummaryCommand,
    MenuCommand,
    ModeCommand,
    ModelCommand,
    NLUDisableCommand,
    NLUEnableCommand,
    NLUStatusCommand,
    NoteAddCommand,
    NoteDeleteCommand,
    NoteListCommand,
    NoteSearchCommand,
    ProjectAnalyzeCommand,
    ProjectArchiveCommand,
    ProjectCreateCommand,
    ProjectDeleteCommand,
    ProjectDiffCommand,
    ProjectFixCommand,
    ProjectListCommand,
    ProjectLoadCommand,
    ProjectProgressCommand,
    ProjectRestoreCommand,
    ProjectReviewCommand,
    ProjectStatsCommand,
    ProjectStatusCommand,
    ProjectTestCommand,
    ProjectValidateCommand,
    PromptCommand,
    RollbackCommand,
    SearchCommand,
    SkillsListCommand,
    SkillsSetCommand,
    SkippedCommand,
    StatusCommand,
    SubscriptionCompareCommand,
    SubscriptionDowngradeCommand,
    SubscriptionStatusCommand,
    SubscriptionTestingModeCommand,
    SubscriptionUpgradeCommand,
    UserArchiveCommand,
    UserCreateCommand,
    UserDeleteCommand,
    UserLoginCommand,
    UserLogoutCommand,
    UserRestoreCommand,
)
from socratic_system.ui.context_display import ContextDisplay
from socratic_system.ui.navigation import NavigationStack
from socratic_system.ui.nlu_handler import NLUHandler, SuggestionDisplay
from socratic_system.utils.logger import get_logger as get_debug_logger
from socratic_system.utils.logger import reset_logger, set_debug_mode

# ============================================================================
# Command Registry Pattern - Consolidated command registration
# ============================================================================
# Registry of (CommandClass, aliases) tuples
# This eliminates ~136 lines of repetitive register_command calls
COMMAND_REGISTRY = [
    # System commands
    (HelpCommand, ["h", "?"]),
    (ExitCommand, ["quit", "q"]),
    (BackCommand, []),
    (MenuCommand, []),
    (StatusCommand, []),
    (ClearCommand, ["cls"]),
    (PromptCommand, []),
    (InfoCommand, []),
    (ModelCommand, []),
    (LLMCommand, ["llm"]),
    # NLU control commands
    (NLUEnableCommand, []),
    (NLUDisableCommand, []),
    (NLUStatusCommand, []),
    # User commands
    (UserLoginCommand, []),
    (UserCreateCommand, []),
    (UserLogoutCommand, []),
    (UserArchiveCommand, []),
    (UserDeleteCommand, []),
    (UserRestoreCommand, []),
    # Subscription commands
    (SubscriptionStatusCommand, []),
    (SubscriptionUpgradeCommand, []),
    (SubscriptionDowngradeCommand, []),
    (SubscriptionCompareCommand, []),
    (SubscriptionTestingModeCommand, []),
    # Project commands
    (ProjectCreateCommand, []),
    (ProjectLoadCommand, []),
    (ProjectListCommand, []),
    (ProjectArchiveCommand, []),
    (ProjectRestoreCommand, []),
    (ProjectDeleteCommand, []),
    # Project analysis/validation commands
    (ProjectAnalyzeCommand, []),
    (ProjectTestCommand, []),
    (ProjectValidateCommand, []),
    (ProjectFixCommand, []),
    (ProjectReviewCommand, []),
    (ProjectDiffCommand, []),
    # GitHub commands
    (GithubImportCommand, []),
    (GithubPullCommand, []),
    (GithubPushCommand, []),
    (GithubSyncCommand, []),
    # Session commands
    (ChatCommand, []),
    (ModeCommand, []),
    (DoneCommand, []),
    (AdvanceCommand, ["phase"]),
    (RollbackCommand, ["phase-back"]),
    (HintCommand, []),
    (SkippedCommand, []),
    # Code commands
    (CodeGenerateCommand, []),
    (CodeDocsCommand, []),
    # Finalize commands (project type-agnostic artifact generation)
    (FinalizeGenerateCommand, []),
    (FinalizeDocsCommand, []),
    # Collaboration commands
    (CollabAddCommand, []),
    (CollabRemoveCommand, []),
    (CollabListCommand, []),
    (CollabRoleCommand, []),
    # Skills commands
    (SkillsSetCommand, []),
    (SkillsListCommand, []),
    # Document commands
    (DocsCommand, []),
    (DocImportCommand, []),
    (DocImportDirCommand, []),
    (DocImportUrlCommand, []),
    (DocListCommand, []),
    (DocPasteCommand, []),
    # File commands
    (FileDeleteCommand, []),
    # Note commands
    (NoteAddCommand, []),
    (NoteListCommand, []),
    (NoteSearchCommand, []),
    (NoteDeleteCommand, []),
    # Conversation commands
    (ConvSearchCommand, []),
    (ConvSummaryCommand, []),
    # Statistics commands
    (ProjectStatsCommand, []),
    (ProjectProgressCommand, []),
    (ProjectStatusCommand, []),
    # Maturity tracking commands
    (MaturityCommand, []),
    (MaturitySummaryCommand, []),
    (MaturityHistoryCommand, []),
    (MaturityStatusCommand, []),
    # Debug commands
    (DebugCommand, []),
    (LogsCommand, []),
    # Query/Answer commands
    (ExplainCommand, []),
    (SearchCommand, []),
]

# Analytics commands (require orchestrator injection)
ANALYTICS_COMMANDS = [
    (AnalyticsAnalyzeCommand, ["aa"]),
    (AnalyticsRecommendCommand, ["ar"]),
    (AnalyticsTrendsCommand, ["at"]),
    (AnalyticsSummaryCommand, ["as"]),
    (AnalyticsBreakdownCommand, ["abd"]),
    (AnalyticsStatusCommand, ["ast"]),
]


class SocraticRAGSystem:
    """Main application class for Socrates AI with command-based interface"""

    def __init__(self, start_frontend: bool = False):
        """Initialize the application

        Args:
            start_frontend: If True, also start the React frontend dev server
        """
        self.orchestrator: AgentOrchestrator | None = None
        self.current_user: User | None = None
        self.current_project: ProjectContext | None = None
        self.session_start: datetime.datetime = datetime.datetime.now()

        # Command system components
        self.command_handler: CommandHandler | None = None
        self.nav_stack: NavigationStack | None = None
        self.context_display: ContextDisplay | None = None
        self.nlu_handler: NLUHandler | None = None

        # Frontend process management
        self.start_frontend = start_frontend
        self.frontend_process: subprocess.Popen | None = None
        self.api_process: subprocess.Popen | None = None

        # Initialize debug logger
        self.logger = get_debug_logger("main_app")

        # Setup signal handlers for graceful shutdown
        signal.signal(signal.SIGINT, self._handle_shutdown)
        signal.signal(signal.SIGTERM, self._handle_shutdown)

    def _handle_shutdown(self, signum, frame):
        """Handle graceful shutdown on Ctrl+C"""
        print(f"\n\n{Fore.YELLOW}Shutting down...{Style.RESET_ALL}")
        self._stop_frontend()
        sys.exit(0)

    def _start_frontend(self) -> bool:
        """Start the React frontend dev server

        Returns:
            True if frontend started successfully, False otherwise
        """
        if not self.start_frontend:
            return True

        print(f"{Fore.CYAN}[Frontend] Starting React dev server...{Style.RESET_ALL}")

        try:
            frontend_dir = Path.cwd() / "socrates-frontend"

            # Check if frontend directory exists
            if not frontend_dir.exists():
                print(
                    f"{Fore.RED}[Frontend] Error: socrates-frontend directory not found{Style.RESET_ALL}"
                )
                return False

            # Check if node_modules exists
            if not (frontend_dir / "node_modules").exists():
                print(f"{Fore.YELLOW}[Frontend] Installing dependencies...{Style.RESET_ALL}")
                result = subprocess.run(
                    ["npm", "install", "--legacy-peer-deps"],
                    cwd=frontend_dir,
                    capture_output=True,
                    text=True,
                )
                if result.returncode != 0:
                    print(f"{Fore.RED}[Frontend] Failed to install dependencies{Style.RESET_ALL}")
                    return False

            # Start frontend development server
            self.frontend_process = subprocess.Popen(
                ["npm", "run", "dev"],
                cwd=frontend_dir,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                env={**os.environ, "VITE_API_URL": "http://localhost:8000"},
            )

            time.sleep(3)  # Give it a moment to start

            if self.frontend_process.poll() is None:
                print(
                    f"{Fore.GREEN}[Frontend] Dev server started (PID: {self.frontend_process.pid}){Style.RESET_ALL}"
                )
                print(f"{Fore.CYAN}[Frontend] Access at: http://localhost:5173{Style.RESET_ALL}")

                # Open browser automatically
                try:
                    time.sleep(1)  # Extra moment to ensure server is fully ready
                    print(f"{Fore.CYAN}[Frontend] Opening browser...{Style.RESET_ALL}")
                    webbrowser.open("http://localhost:5173")
                except Exception as e:
                    print(
                        f"{Fore.YELLOW}[Frontend] Could not open browser automatically: {e}{Style.RESET_ALL}"
                    )
                    print(
                        f"{Fore.YELLOW}[Frontend] Please open manually: http://localhost:5173{Style.RESET_ALL}"
                    )

                return True
            else:
                print(f"{Fore.RED}[Frontend] Failed to start development server{Style.RESET_ALL}")
                return False

        except Exception as e:
            print(f"{Fore.RED}[Frontend] Error starting frontend: {e}{Style.RESET_ALL}")
            return False

    def _stop_frontend(self) -> None:
        """Stop the frontend development server"""
        if self.frontend_process and self.frontend_process.poll() is None:
            print(f"{Fore.YELLOW}[Frontend] Stopping dev server...{Style.RESET_ALL}")
            try:
                self.frontend_process.terminate()
                self.frontend_process.wait(timeout=5)
                print(f"{Fore.GREEN}[Frontend] Dev server stopped{Style.RESET_ALL}")
            except subprocess.TimeoutExpired:
                self.frontend_process.kill()
            except Exception as e:
                print(f"{Fore.RED}[Frontend] Error stopping frontend: {e}{Style.RESET_ALL}")

    def start(self) -> None:
        """Start the Socrates AI"""
        self._print_banner()

        # Start frontend if requested
        if self.start_frontend:
            if not self._start_frontend():
                print(f"{Fore.YELLOW}Continuing without frontend...{Style.RESET_ALL}")

        # Reset logger singleton to clear old handlers and use new rotation handler
        reset_logger()
        # Reinitialize logger after reset
        self.logger = get_debug_logger("main_app")

        # Disable debug mode by default (user can toggle with /debug command)
        set_debug_mode(False)

        # Get API key or choose authentication mode
        api_key = self._get_api_key()
        if not api_key:
            print(f"{Fore.RED}No API key provided. Exiting...")
            self._stop_frontend()
            return

        try:
            # Initialize orchestrator
            print(f"\n{Fore.YELLOW}Initializing system...{Style.RESET_ALL}")

            # Validate API key before initializing orchestrator
            api_key = self._validate_api_key_or_subscription(api_key)

            # In subscription mode, use a placeholder API key - actual auth happens during user login
            if api_key == "subscription_mode":
                # For subscription mode, we'll use a dummy key initially
                # It will be validated during user authentication
                self.orchestrator = AgentOrchestrator("subscription_placeholder_key")
            else:
                self.orchestrator = AgentOrchestrator(api_key)

            # Initialize command system components
            self.command_handler = CommandHandler()
            self.nav_stack = NavigationStack()
            self.nav_stack.push("main_menu", {})  # Initialize with main menu as root context
            self.logger.debug("Navigation stack initialized with main_menu as root")
            self.context_display = ContextDisplay()

            # Register all commands
            self._register_commands()

            # Authenticate user
            if not self._authenticate_user():
                return

            # Enable NLU after login
            self.nlu_handler = NLUHandler(self.orchestrator.claude_client, self.command_handler)
            print(f"{Fore.GREEN}[OK] Natural language understanding enabled{Style.RESET_ALL}")

            # Start command loop
            self._command_loop()

        except Exception as e:
            print(f"{Fore.RED}System error: {e}{Style.RESET_ALL}")

    def _print_banner(self) -> None:
        """Display the application banner"""
        print(f"{Fore.CYAN}{Style.BRIGHT}")
        print("╔═══════════════════════════════════════════════╗")
        print("║             Socrates RAG System               ║")
        print("║Οὐδὲν οἶδα, οὔτε διδάσκω τι, ἀλλὰ διαπορῶ μόνον║")
        print("╚═══════════════════════════════════════════════╝")
        print(f"{Style.RESET_ALL}")

    def _get_api_key(self) -> str | None:
        """Get Claude API key from environment or user input"""
        api_key = os.getenv("API_KEY_CLAUDE")
        if not api_key:
            print(f"\n{Fore.CYAN}API Configuration{Style.RESET_ALL}")
            print(f"{Fore.YELLOW}Choose how to authenticate with Claude:{Style.RESET_ALL}")
            print("1. Use API Key directly")
            print("2. Use Subscription (requires valid account)")

            choice = input(f"\n{Fore.WHITE}Select option (1 or 2): ").strip()

            if choice == "2":
                # Subscription mode - will be handled by authenticating user first
                print(
                    f"{Fore.CYAN}Using subscription mode - authenticate with your account{Style.RESET_ALL}"
                )
                # Store a marker that we're in subscription mode
                os.environ["SOCRATES_SUBSCRIPTION_MODE"] = "1"
                # Return a placeholder - actual API key will be obtained from orchestrator
                return "subscription_mode"
            else:
                # API Key mode
                print(
                    f"{Fore.CYAN}Paste your Claude API key (or set API_KEY_CLAUDE env var):{Style.RESET_ALL}"
                )
                api_key = getpass.getpass("Claude API Key: ")
                if api_key:
                    os.environ["SOCRATES_SUBSCRIPTION_MODE"] = "0"
        return api_key

    def _validate_api_key_or_subscription(self, api_key: str | None) -> str:
        """
        Validate API key format or subscription mode before initialization.

        Args:
            api_key: API key string, "subscription_mode", or None

        Returns:
            Validated API key or "subscription_mode"

        Raises:
            ValueError: If API key format is invalid
        """
        # Subscription mode is always valid
        if api_key == "subscription_mode":
            return api_key

        # API key mode requires non-empty string
        if not api_key:
            raise ValueError(
                "No API key provided and not in subscription mode.\n"
                "Please provide a valid Claude API key or choose subscription mode."
            )

        # Validate API key format
        if not isinstance(api_key, str):
            raise ValueError(f"API key must be a string, got {type(api_key).__name__}")

        if not (api_key.startswith("sk-ant-") or api_key.startswith("sk-")):
            raise ValueError(
                f"Invalid API key format. Expected key starting with 'sk-ant-' or 'sk-'\n"
                f"Got: {api_key[:20]}{'...' if len(api_key) > 20 else ''}"
            )

        return api_key

    def _authenticate_user(self) -> bool:
        """Handle user login or registration"""
        print(f"\n{Fore.CYAN}Authentication{Style.RESET_ALL}\n")

        while True:
            print(f"{Fore.YELLOW}Options:{Style.RESET_ALL}")
            print("1. Login with existing account (/user login)")
            print("2. Create new account (/user create)")
            print("3. Exit (/exit)")

            choice = input(f"\n{Fore.WHITE}Choose option (1-3): ").strip()

            if choice == "1" or choice.startswith("/user login"):
                result = UserLoginCommand().execute([], self._get_context())
                if result["status"] == "success":
                    self.current_user = result["data"]["user"]
                    self.context_display.set_context(user=self.current_user)
                    self._apply_testing_mode_if_enabled()
                    return True
                else:
                    if result.get("message"):
                        print(result["message"])

            elif choice == "2" or choice.startswith("/user create"):
                result = UserCreateCommand().execute([], self._get_context())
                if result["status"] == "success":
                    self.current_user = result["data"]["user"]
                    self.context_display.set_context(user=self.current_user)
                    self._apply_testing_mode_if_enabled()
                    return True
                else:
                    if result.get("message"):
                        print(result["message"])

            elif choice == "3" or choice == "/exit":
                print(f"\n{Fore.GREEN}Thank you for using Socrates AI")
                print("..τῷ Ἀσκληπιῷ ὀφείλομεν ἀλεκτρυόνα, ἀπόδοτε καὶ μὴ ἀμελήσετε...\n")
                return False

            else:
                print(f"{Fore.RED}Invalid choice. Please try again.{Style.RESET_ALL}")

    def _apply_testing_mode_if_enabled(self) -> None:
        """Enable testing mode for current user if --testing flag was provided."""
        if os.getenv("SOCRATES_TESTING_MODE") == "1" and self.current_user:
            if not self.current_user.testing_mode:
                self.current_user.testing_mode = True
                self.current_user.testing_mode_enabled_at = datetime.datetime.now()
                self.orchestrator.database.save_user(self.current_user)
                print(f"\n{Fore.GREEN}[OK] Testing mode ENABLED{Style.RESET_ALL}")
                print(f"{Fore.YELLOW}All monetization restrictions bypassed!{Style.RESET_ALL}\n")

    def _register_commands(self) -> None:
        """Register all available commands using registry pattern"""
        # Register standard commands from registry
        for command_class, aliases in COMMAND_REGISTRY:
            command_instance = command_class()
            if aliases:
                self.command_handler.register_command(command_instance, aliases=aliases)
            else:
                self.command_handler.register_command(command_instance)

        # Register analytics commands (require orchestrator injection)
        for command_class, aliases in ANALYTICS_COMMANDS:
            command_instance = command_class(self.orchestrator)
            if aliases:
                self.command_handler.register_command(command_instance, aliases=aliases)
            else:
                self.command_handler.register_command(command_instance)

    def _handle_command_result(self, result: dict[str, Any]) -> bool:
        """
        Handle command execution result and display appropriate messages.

        Args:
            result: Command result dictionary with status and message

        Returns:
            True if command loop should continue, False if exit requested
        """
        status = result["status"]

        if status == "exit":
            return False

        # Display message if present
        if result.get("message"):
            self._display_status_message(status, result["message"])

        # Handle success status with data processing
        if status == "success":
            self._handle_success_result(result.get("data", {}))
        elif status not in ("info", "idle", "error"):
            # Unknown status
            print(f"{Fore.YELLOW}Command executed with status: {status}{Style.RESET_ALL}")

        return True

    def _display_status_message(self, status: str, message: str) -> None:
        """Display message based on status type"""
        if status == "unknown":
            return  # Don't duplicate unknown messages
        print(message)

    def _handle_success_result(self, data: dict[str, Any]) -> None:
        """Handle success status data and navigation"""
        # Handle project entry (push to navigation stack)
        project = data.get("project")
        if project:
            self._handle_project_entry(project)

        # Handle navigation context changes (from /back, /menu commands)
        nav_context = data.get("nav_context")
        if nav_context:
            self._handle_nav_context(nav_context)

        # Check if session ended (done command, menu command, back command)
        if data.get("session_ended"):
            self._clear_session()

    def _handle_project_entry(self, project) -> None:
        """Handle entering a project context"""
        if self.nav_stack:
            self.nav_stack.push("project_view", {"project_id": project.project_id})
            self.logger.debug(f"Entered project context: {project.name} (ID: {project.project_id})")

    def _handle_nav_context(self, nav_context: str) -> None:
        """Handle navigation context changes"""
        if nav_context == "main_menu":
            self.current_project = None
            self.context_display.set_context(clear_project=True)
            self.logger.debug("Returned to main_menu context")

    def _clear_session(self) -> None:
        """Clear session data when session ends"""
        self.current_project = None
        self.context_display.set_context(clear_project=True)
        self.logger.debug("Session ended - cleared project and context display")

    def _command_loop(self) -> None:
        """Main command processing loop"""
        while True:
            try:
                # Display context
                prompt = self.context_display.get_prompt()
                user_input = input(prompt).strip()

                if not user_input:
                    continue

                # Execute command (with NLU support)
                result = self._process_input_with_nlu(user_input, self._get_context())

                # Handle result
                if not self._handle_command_result(result):
                    break

            except KeyboardInterrupt:
                print(f"\n{Fore.YELLOW}Interrupted. Type '/exit' to quit.{Style.RESET_ALL}")
            except Exception as e:
                print(f"{Fore.RED}Unexpected error: {e}{Style.RESET_ALL}")

    def _process_input_with_nlu(self, user_input: str, context: dict[str, Any]) -> dict[str, Any]:
        """Process input with NLU support"""
        # Skip NLU if not initialized or input starts with /
        if not self.nlu_handler or self.nlu_handler.should_skip_nlu(user_input):
            return self.command_handler.execute(user_input, context)

        # Interpret with NLU
        nlu_result = self.nlu_handler.interpret(user_input, context)

        if nlu_result["status"] == "success":
            # High confidence - execute directly
            command = nlu_result["command"]
            if nlu_result.get("message"):
                print(nlu_result["message"])
            return self.command_handler.execute(command, context)

        elif nlu_result["status"] == "suggestions":
            # Medium confidence - show suggestions
            if nlu_result.get("message"):
                print(nlu_result["message"])
            suggestions = nlu_result.get("suggestions", [])
            selected = SuggestionDisplay.show_suggestions(suggestions)

            if selected:
                print(f"{Fore.CYAN}[NLU] Executing: {selected}{Style.RESET_ALL}")
                return self.command_handler.execute(selected, context)
            return {"status": "idle"}

        else:  # no_match or error
            return {
                "status": "error",
                "message": nlu_result.get("message", "Couldn't understand that."),
            }

    def _get_context(self) -> dict[str, Any]:
        """Get the current application context for commands"""
        # Reload user from database to ensure we have the latest state
        # This is necessary because commands may modify the user (e.g., testing mode)
        # and save to the database without updating self.current_user in memory
        if self.current_user:
            self.current_user = self.orchestrator.database.load_user(self.current_user.username)

        return {
            "user": self.current_user,
            "project": self.current_project,
            "orchestrator": self.orchestrator,
            "nav_stack": self.nav_stack,
            "app": self,
        }


def main():
    """Main entry point for the application

    Command line arguments:
        --frontend or -f: Start the React frontend dev server along with the CLI
        --help or -h: Show this help message

    Examples:
        python -m socratic_system.ui.main_app                 # CLI only
        python -m socratic_system.ui.main_app --frontend      # CLI + Frontend
        python -m socratic_system.ui.main_app -f              # CLI + Frontend (short form)
    """
    # Check for command line arguments
    start_frontend = "--frontend" in sys.argv or "-f" in sys.argv
    show_help = "--help" in sys.argv or "-h" in sys.argv

    if show_help:
        print(main.__doc__)
        sys.exit(0)

    # Create and start application
    app = SocraticRAGSystem(start_frontend=start_frontend)
    app.start()


if __name__ == "__main__":
    main()
