"""Claude Code service for local CLI execution through the tunnel.

This service provides an HTTP API that the backend can call through the tunnel
to execute Claude Code CLI commands in a specific working directory.
"""

import asyncio
import logging
import os
import subprocess
from pathlib import Path
from typing import Optional
from aiohttp import web

logger = logging.getLogger(__name__)


def find_claude_cli() -> str:
    """Find the Claude CLI executable.
    
    Checks:
    1. CLAUDE_CLI_NAME environment variable (if absolute path, use it directly)
    2. ~/.claude/local/claude (local install)
    3. 'claude' command in PATH
    
    Returns:
        Path or command name for Claude CLI
        
    Raises:
        ValueError: If Claude CLI cannot be found
    """
    custom_cli_name = os.environ.get("CLAUDE_CLI_NAME")
    if custom_cli_name:
        if os.path.isabs(custom_cli_name):
            if os.path.exists(custom_cli_name):
                return custom_cli_name
            raise ValueError(f"Claude CLI not found at specified path: {custom_cli_name}")
        
        if custom_cli_name.startswith("./") or custom_cli_name.startswith("../") or "/" in custom_cli_name:
            raise ValueError(
                f"Invalid CLAUDE_CLI_NAME: Relative paths are not allowed. "
                f"Use either a simple name (e.g., 'claude') or an absolute path."
            )
        cli_name = custom_cli_name
    else:
        cli_name = "claude"
    
    # Try local install path: ~/.claude/local/claude
    home_dir = Path.home()
    local_path = home_dir / ".claude" / "local" / "claude"
    if local_path.exists():
        return str(local_path)
    
    # Fallback to CLI name (PATH lookup)
    try:
        result = subprocess.run(
            [cli_name, "--version"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if result.returncode == 0:
            return cli_name
    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass
    
    raise ValueError(
        f"Claude CLI not found. Please install Claude CLI:\n"
        f"1. Install: npm install -g @anthropic-ai/claude-cli\n"
        f"2. Or set CLAUDE_CLI_NAME environment variable to the CLI path\n"
        f"3. Or ensure 'claude' is in your PATH"
    )


class ClaudeCodeService:
    """Manages local Claude Code CLI execution via HTTP API."""
    
    def __init__(self, port: int = 9300):
        self.port = port
        self.app: Optional[web.Application] = None
        self.runner: Optional[web.AppRunner] = None
        self.running = False
    
    async def start(self):
        """Start the Claude Code HTTP service."""
        if self.running:
            logger.warning("Claude Code service already running")
            return
        
        try:
            logger.info(f"Starting Claude Code service on localhost:{self.port}")
            
            self.app = web.Application()
            self.app.router.add_post('/run', self.handle_run)
            self.app.router.add_post('/validate', self.handle_validate)
            
            self.runner = web.AppRunner(self.app)
            await self.runner.setup()
            site = web.TCPSite(self.runner, 'localhost', self.port)
            await site.start()
            
            self.running = True
            logger.info(f"✅ Claude Code service started on localhost:{self.port}")
            
        except Exception as e:
            logger.error(f"Failed to start Claude Code service: {e}")
            await self.cleanup()
            raise
    
    async def handle_run(self, request: web.Request) -> web.Response:
        """Execute Claude Code CLI with a prompt in a working directory."""
        try:
            data = await request.json()
            
            prompt = data.get('prompt')
            work_folder = data.get('workFolder')
            timeout_ms = data.get('timeout_ms', 1800000)  # 30 minutes default
            
            if not prompt:
                return web.json_response({
                    "success": False,
                    "error": "prompt is required"
                }, status=400)
            
            if not work_folder:
                return web.json_response({
                    "success": False,
                    "error": "workFolder is required"
                }, status=400)
            
            # Security: Only allow absolute paths
            if not os.path.isabs(work_folder):
                return web.json_response({
                    "success": False,
                    "error": "Only absolute file paths are allowed for workFolder"
                }, status=400)
            
            # Verify folder exists
            if not os.path.exists(work_folder):
                return web.json_response({
                    "success": False,
                    "error": f"workFolder does not exist: {work_folder}"
                }, status=400)
            
            if not os.path.isdir(work_folder):
                return web.json_response({
                    "success": False,
                    "error": f"workFolder is not a directory: {work_folder}"
                }, status=400)
            
            logger.info(f"Executing Claude Code in {work_folder}: {prompt[:100]}...")
            
            # Find Claude CLI
            try:
                claude_cli_path = find_claude_cli()
            except ValueError as e:
                return web.json_response({
                    "success": False,
                    "error": str(e)
                }, status=400)
            
            # Execute Claude CLI
            claude_args = ["--dangerously-skip-permissions", "-p", prompt]
            timeout_seconds = timeout_ms / 1000
            
            try:
                result = await asyncio.to_thread(
                    subprocess.run,
                    [claude_cli_path] + claude_args,
                    cwd=work_folder,
                    capture_output=True,
                    text=True,
                    timeout=timeout_seconds,
                )
                
                if result.returncode != 0:
                    return web.json_response({
                        "success": False,
                        "error": f"Claude CLI execution failed with exit code {result.returncode}",
                        "stderr": result.stderr,
                        "stdout": result.stdout,
                    }, status=400)
                
                return web.json_response({
                    "success": True,
                    "output": result.stdout,
                    "stderr": result.stderr if result.stderr else None,
                })
                
            except subprocess.TimeoutExpired:
                return web.json_response({
                    "success": False,
                    "error": f"Claude CLI command timed out after {timeout_seconds}s"
                }, status=408)
            except FileNotFoundError:
                return web.json_response({
                    "success": False,
                    "error": f"Claude CLI not found at: {claude_cli_path}"
                }, status=400)
            except Exception as e:
                logger.error(f"Error executing Claude CLI: {e}")
                return web.json_response({
                    "success": False,
                    "error": f"Unexpected error executing Claude CLI: {str(e)}"
                }, status=500)
        
        except Exception as e:
            logger.error(f"Error handling run request: {e}")
            return web.json_response({
                "success": False,
                "error": str(e)
            }, status=500)
    
    async def handle_validate(self, request: web.Request) -> web.Response:
        """Validate that Claude CLI works and workFolder is accessible."""
        logger.info("🔵 Claude Code validate handler CALLED")
        try:
            logger.info("🔵 Claude Code validate handler: About to await request.json()")
            data = await request.json()
            logger.info("🔵 Claude Code validate handler: Got JSON data: %s", data)
            
            work_folder = data.get('workFolder')
            logger.info("Claude Code validate request received workFolder=%s", work_folder)
            
            if not work_folder:
                return web.json_response({
                    "valid": False,
                    "error": "workFolder is required"
                }, status=400)
            
            # Security: Only allow absolute paths
            if not os.path.isabs(work_folder):
                return web.json_response({
                    "valid": False,
                    "error": "Only absolute file paths are allowed for workFolder"
                }, status=400)
            
            # Verify folder exists
            if not os.path.exists(work_folder):
                return web.json_response({
                    "valid": False,
                    "error": f"workFolder does not exist: {work_folder}"
                })
            
            if not os.path.isdir(work_folder):
                return web.json_response({
                    "valid": False,
                    "error": f"workFolder is not a directory: {work_folder}"
                })
            
            # Check if folder is readable
            if not os.access(work_folder, os.R_OK):
                return web.json_response({
                    "valid": False,
                    "error": f"workFolder is not readable: {work_folder}"
                })
            
            # Find Claude CLI
            try:
                claude_cli_path = find_claude_cli()
            except ValueError as e:
                return web.json_response({
                    "valid": False,
                    "error": str(e)
                })
            logger.info("Claude Code validate using cli=%s", claude_cli_path)
            
            # Validate CLI availability without triggering auth flows.
            try:
                result = await asyncio.to_thread(
                    subprocess.run,
                    [claude_cli_path, "--version"],
                    cwd=work_folder,
                    capture_output=True,
                    text=True,
                    timeout=10.0,
                )
            except subprocess.TimeoutExpired:
                return web.json_response({
                    "valid": False,
                    "error": "Claude CLI validation timed out"
                })
            except Exception as e:
                logger.error(f"Error validating Claude CLI: {e}")
                return web.json_response({
                    "valid": False,
                    "error": f"Validation error: {str(e)}"
                })

            if result.returncode != 0:
                logger.warning(
                    f"Claude CLI --version failed: {result.stderr or result.stdout or 'Unknown error'}"
                )
                return web.json_response({
                    "valid": False,
                    "error": f"Claude CLI validation failed: {result.stderr or result.stdout or 'Unknown error'}"
                })

            logger.info("Claude CLI --version OK: %s", result.stdout.strip())
            return web.json_response({
                "valid": True,
                "claude_cli_path": claude_cli_path,
                "work_folder": work_folder,
            })
        
        except Exception as e:
            logger.error(f"Error handling validate request: {e}")
            return web.json_response({
                "valid": False,
                "error": str(e)
            }, status=500)
    
    async def stop(self):
        """Stop the Claude Code service."""
        if not self.running:
            logger.warning("Claude Code service not running")
            return
        
        logger.info("Stopping Claude Code service...")
        await self.cleanup()
        logger.info("✅ Claude Code service stopped")
    
    async def cleanup(self):
        """Clean up resources."""
        self.running = False
        
        if self.runner:
            try:
                await self.runner.cleanup()
            except Exception as e:
                logger.error(f"Error cleaning up runner: {e}")
            self.runner = None
        
        self.app = None
    
    def get_status(self) -> dict:
        """Get current Claude Code service status."""
        return {
            "enabled": self.running,
            "port": self.port,
        }
