"""Abstract base class for Codex AI agents."""
from __future__ import annotations

import asyncio
import logging
import time
from abc import ABC, abstractmethod
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

from mesh.codex.models import (
    CodexAgentInfo, CodexAgentStatus, CodexTask, CodexTaskResult,
    CodexTaskStatus,
)

logger = logging.getLogger(__name__)

# Safety limits
MAX_EXECUTION_TIME_S = 300  # 5 minutes
MAX_OUTPUT_SIZE_BYTES = 10 * 1024 * 1024  # 10 MB
MAX_FILE_READ_BYTES = 5 * 1024 * 1024  # 5 MB per file


class CodexAgent(ABC):
    """Base class for all Codex AI agents.

    Provides common infrastructure: execution logging, file reading with
    safety limits, and timeout enforcement.
    """

    def __init__(self, agent_type: str, mesh_db, config=None):
        self.agent_type = agent_type
        self.mesh_db = mesh_db
        self.config = config
        self._info = CodexAgentInfo(agent_type=agent_type)

    @property
    def info(self) -> CodexAgentInfo:
        return self._info

    @abstractmethod
    async def execute(self, task: CodexTask) -> CodexTaskResult:
        """Execute a task and return structured results.

        Subclasses must implement this. The orchestrator wraps calls
        with timeout enforcement and error handling.
        """
        ...

    async def safe_execute(self, task: CodexTask) -> CodexTaskResult:
        """Execute with timeout and error handling."""
        self._info.status = CodexAgentStatus.BUSY
        self._info.last_active = datetime.now(timezone.utc)
        start = time.monotonic()

        timeout = task.parameters.get("timeout_s", MAX_EXECUTION_TIME_S)
        try:
            result = await asyncio.wait_for(self.execute(task), timeout=timeout)
            result.duration_s = time.monotonic() - start
            self._info.tasks_completed += 1
            self._log_execution(task, result)
            return result
        except asyncio.TimeoutError:
            self._info.tasks_failed += 1
            result = CodexTaskResult(
                task_id=task.task_id,
                success=False,
                error=f"Execution timed out after {timeout}s",
                duration_s=time.monotonic() - start,
            )
            self._log_execution(task, result)
            return result
        except Exception as e:
            self._info.tasks_failed += 1
            logger.exception("Agent %s failed on task %s", self.agent_type, task.task_id)
            result = CodexTaskResult(
                task_id=task.task_id,
                success=False,
                error=str(e),
                duration_s=time.monotonic() - start,
            )
            self._log_execution(task, result)
            return result
        finally:
            self._info.status = CodexAgentStatus.IDLE

    def _log_execution(self, task: CodexTask, result: CodexTaskResult):
        """Record execution to the database."""
        try:
            if self.mesh_db:
                self.mesh_db.update_codex_task(
                    task.task_id,
                    status=CodexTaskStatus.COMPLETED.value if result.success
                           else CodexTaskStatus.FAILED.value,
                    result_json={
                        "success": result.success,
                        "findings_count": len(result.findings),
                        "recommendations_count": len(result.recommendations),
                        "artifacts": result.artifacts,
                        "error": result.error,
                        "duration_s": result.duration_s,
                    },
                )
                # Record individual findings
                for finding in result.findings:
                    self.mesh_db.record_finding(
                        task_id=task.task_id,
                        severity=finding.get("severity", "info"),
                        category=finding.get("category", "general"),
                        location=finding.get("location", ""),
                        message=finding.get("message", ""),
                        suggestion=finding.get("suggestion", ""),
                    )
        except Exception as e:
            logger.warning("Failed to log execution for task %s: %s", task.task_id, e)

    def _get_context(self, target: str) -> Optional[str]:
        """Read a file safely with size limits. Returns content or None."""
        path = Path(target)
        if not path.exists():
            return None
        if not path.is_file():
            return None
        size = path.stat().st_size
        if size > MAX_FILE_READ_BYTES:
            logger.warning("File %s too large (%d bytes), skipping", target, size)
            return None
        try:
            return path.read_text(encoding="utf-8", errors="replace")
        except Exception as e:
            logger.warning("Cannot read %s: %s", target, e)
            return None

    def _collect_python_files(self, target: str, max_files: int = 200) -> list[Path]:
        """Collect Python files from a target path (file or directory)."""
        path = Path(target)
        if path.is_file() and path.suffix == ".py":
            return [path]
        if path.is_dir():
            files = sorted(path.rglob("*.py"))
            # Skip common non-source directories
            skip = {"__pycache__", ".venv", "venv", "node_modules", ".git", ".eggs"}
            files = [f for f in files if not any(s in f.parts for s in skip)]
            return files[:max_files]
        return []
