"""Execution wrapper mode - track job executions."""

import logging
import sys
import os
import time
import subprocess
import hashlib
from typing import List, Optional
from runsentry.config import get_config
from runsentry.transport.client import APIClient
from runsentry.storage.inventory import load_inventory

logger = logging.getLogger(__name__)


def _generate_job_fingerprint(command: List[str]) -> str:
    """Generate job fingerprint from command."""
    command_str = " ".join(command)
    return hashlib.sha256(command_str.encode()).hexdigest()


def _find_job_external_id(command_args: List[str]) -> Optional[str]:
    """
    Find the external ID for a command by matching it against discovered jobs.
    
    Returns the externalId from the inventory if a matching job is found,
    otherwise returns None (caller should generate one).
    
    Matching strategies (in order):
    1. Exact command string match
    2. Command appears at the end of discovered command (handles wrapped commands)
    3. CommandHash match (most robust, handles formatting differences)
    """
    command_str = " ".join(command_args)
    command_hash = hashlib.sha256(command_str.encode()).hexdigest()[:12]
    
    try:
        inventory = load_inventory()
        
        # First pass: exact match
        for job in inventory.values():
            if isinstance(job, dict):
                job_command = job.get("command", "").strip()
                if job_command == command_str.strip():
                    external_id = job.get("externalId")
                    if external_id:
                        logger.debug(f"Found exact command match in inventory: {external_id}")
                        return external_id
        
        # Second pass: command appears at end (handles wrapped commands like "runsentry exec <command>")
        for job in inventory.values():
            if isinstance(job, dict):
                job_command = job.get("command", "").strip()
                # Check if our command appears at the end of the discovered command
                if job_command.endswith(command_str.strip()) or job_command.endswith(f" {command_str.strip()}"):
                    external_id = job.get("externalId")
                    if external_id:
                        logger.debug(f"Found wrapped command match in inventory: {external_id}")
                        return external_id
        
        # Third pass: commandHash match (most robust, handles whitespace/formatting differences)
        for job in inventory.values():
            if isinstance(job, dict):
                job_command_hash = job.get("commandHash", "")
                if job_command_hash == command_hash:
                    external_id = job.get("externalId")
                    if external_id:
                        logger.debug(f"Found commandHash match in inventory: {external_id}")
                        return external_id
                        
    except Exception as e:
        logger.warning(f"Failed to lookup job in inventory: {e}")
    
    return None


def run_exec(command_args: List[str]):
    """Execute a command and track its execution."""
    if not command_args:
        logger.error("No command provided")
        sys.exit(1)

    config = get_config()
    if not config.is_configured():
        logger.warning("Agent not configured. Execution will not be tracked.")
        # Still execute the command
        _execute_command(command_args)
        return

    client = APIClient()

    # Try to find the job's external ID from inventory first
    # This ensures we use the same externalId that was used during discovery
    job_external_id = _find_job_external_id(command_args)
    
    # Fallback to generating one if not found in inventory
    if not job_external_id:
        job_external_id = _generate_job_fingerprint(command_args)
        logger.debug(f"Job not found in inventory, using generated externalId: {job_external_id}")

    # Send execution started event
    start_time = time.time()
    start_event = {
        "jobExternalId": job_external_id,
        "event": "started",
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(start_time)),
    }

    execution_id = None
    try:
        response = client.send_execution_event(start_event)
        if response and hasattr(response, 'json'):
            data = response.json()
            execution_id = data.get("executionId")
    except Exception as e:
        logger.warning(f"Failed to send start event: {e}")

    # Execute the command and capture stdout/stderr
    exit_code, stdout, stderr = _execute_command(command_args)

    # Calculate duration
    end_time = time.time()

    # Determine status based on exit code
    status = "success" if exit_code == 0 else "failed"

    # Send execution finished event
    finish_event = {
        "jobExternalId": job_external_id,
        "event": "finished",
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(end_time)),
        "status": status,
        "exitCode": exit_code,
        "stdout": stdout,
        "stderr": stderr,
    }

    # Include executionId if we got it from start event
    if execution_id:
        finish_event["executionId"] = execution_id

    try:
        client.send_execution_event(finish_event)
    except Exception as e:
        logger.warning(f"Failed to send finish event: {e}")

    # Exit with the same code as the command
    sys.exit(exit_code)


def _execute_command(command_args: List[str]) -> tuple[int, str, str]:
    """Execute a command and return exit code, stdout, and stderr."""
    try:
        # Execute command and capture output
        result = subprocess.run(
            command_args,
            stdin=sys.stdin,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
        )
        
        # Decode output (already text mode, but ensure strings)
        stdout = result.stdout or ""
        stderr = result.stderr or ""
        
        # Print output to console so user sees it
        if stdout:
            print(stdout, end='', file=sys.stdout)
        if stderr:
            print(stderr, end='', file=sys.stderr)
        
        return result.returncode, stdout, stderr
    except FileNotFoundError:
        error_msg = f"Command not found: {command_args[0]}"
        logger.error(error_msg)
        print(error_msg, file=sys.stderr)
        return 127, "", error_msg
    except Exception as e:
        error_msg = f"Failed to execute command: {e}"
        logger.error(error_msg, exc_info=True)
        print(error_msg, file=sys.stderr)
        return 1, "", error_msg

