#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Pipeline service for Agent Control Hub
Handles the complete project processing pipeline
"""
import os
import json
import asyncio
import sys
import logging
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Optional, Any

# Add project root to Python path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))

# Import project dependencies
from services.plan import AgentProject, ProjectStatus
from utils.env import ensure_python_venv, run_command
from utils.files import run_python_snippet_in_dir, extract_python_code_block

# Configuration
PROJECTS_DIR = Path("workspace/projects")
WORKSPACE_DIR = Path("workspace")
TEMP_TTL_MINUTES = 60

# Configure logging
# Try to create logs directory and set up file logging, fallback to console only
handlers = [logging.StreamHandler()]

try:
    os.makedirs("logs", exist_ok=True)
    log_file = "logs/agent_hub.log"
    # Test if we can actually write to the file
    with open(log_file, "a") as f:
        f.write("")  # Test write
    handlers.append(logging.FileHandler(log_file))
except Exception:
    # If file logging fails, just use console logging
    pass

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=handlers,
)

logger = logging.getLogger(__name__)

# Global projects store (will be set by the main app)
projects: Dict[str, AgentProject] = {}


def _dir_age_minutes(path: Path) -> float:
    try:
        mtime = path.stat().st_mtime
    except FileNotFoundError:
        return float("inf")
    return (datetime.now().timestamp() - mtime) / 60


async def cleanup_old_projects(ttl_minutes: int = TEMP_TTL_MINUTES) -> None:
    """Delete project directories older than TTL and prune in-memory records."""
    try:
        for sub in PROJECTS_DIR.iterdir():
            if sub.is_dir() and _dir_age_minutes(sub) > ttl_minutes:
                import shutil

                try:
                    shutil.rmtree(sub)
                    # remove project record if exists
                    pid = sub.name
                    if pid in projects:
                        del projects[pid]
                except Exception as e:
                    pass
    except Exception as e:
        pass


async def periodic_cleanup_task(interval_minutes: int = 10):
    while True:
        await cleanup_old_projects()
        await asyncio.sleep(max(60, interval_minutes * 60))


def minimal_scaffold_snippet(project_dir_str: str) -> str:
    return (
        "import os, json\n"
        f"project_dir = r'{project_dir_str}'\n"
        "os.makedirs(project_dir, exist_ok=True)\n"
        "readme = os.path.join(project_dir, 'README.md')\n"
        "app_py = os.path.join(project_dir, 'app.py')\n"
        "with open(readme, 'w', encoding='utf-8') as f:\n"
        "    f.write('# Project\n\nGenerated by Agent Hub. Stdlib-only scaffold.')\n"
        "with open(app_py, 'w', encoding='utf-8') as f:\n"
        "    f.write('print(\"Hello from Agent Hub!\")')\n"
        "print('FILE_CREATED:' + readme)\n"
        "print('FILE_CREATED:' + app_py)\n"
    )


def _write_language_scaffold(project_dir: Path, language: str):
    """Write language-specific scaffold files directly"""
    if language.lower() == "python":
        (project_dir / "README.md").write_text(
            "# Project\n\nGenerated by Agent Hub.", encoding="utf-8"
        )
        (project_dir / "app.py").write_text(
            'print("Hello from Agent Hub!")', encoding="utf-8"
        )
    elif language.lower() in ("node", "javascript"):
        (project_dir / "package.json").write_text(
            '{"name": "project", "version": "1.0.0", "main": "index.js"}',
            encoding="utf-8",
        )
        (project_dir / "index.js").write_text(
            'console.log("Hello from Agent Hub!");', encoding="utf-8"
        )
    elif language.lower() in ("react-ts", "react"):
        (project_dir / "package.json").write_text(
            '{"name": "project", "version": "1.0.0", "dependencies": {"react": "^18.0.0"}}',
            encoding="utf-8",
        )
        (project_dir / "src" / "App.tsx").parent.mkdir(parents=True, exist_ok=True)
        (project_dir / "src" / "App.tsx").write_text(
            'import React from "react";\nexport default function App() { return <div>Hello from Agent Hub!</div>; }',
            encoding="utf-8",
        )
    else:
        # default python
        (project_dir / "app.py").write_text(
            'print("Hello from Agent Hub!")', encoding="utf-8"
        )


def _force_minimal_scaffold(agent: AgentProject) -> bool:
    """Write a minimal scaffold into the project sandbox and update files_created."""
    project_dir = PROJECTS_DIR / agent.project_id
    project_dir.mkdir(parents=True, exist_ok=True)
    snippet = minimal_scaffold_snippet(project_dir.as_posix())
    result = run_python_snippet_in_dir(snippet, cwd=project_dir)
    # Record snippet outputs for debugging
    try:
        agent.execution_log.append(
            "SCAFFOLD_STDOUT: " + (result.get("stdout", "").strip())
        )
        if result.get("stderr"):
            agent.execution_log.append(
                "SCAFFOLD_STDERR: " + result.get("stderr", "").strip()
            )
    except Exception:
        pass

    def _collect() -> list[str]:
        files: list[str] = []
        if project_dir.exists():
            for q in project_dir.rglob("*"):
                if q.is_file():
                    files.append(str(q.relative_to(WORKSPACE_DIR)))
        return files

    created_files = _collect()

    # Fallback: if snippet failed or produced no files, write minimal files directly
    if result.get("returncode", 1) != 0 or len(created_files) == 0:
        try:
            _write_language_scaffold(project_dir, getattr(agent, "language", "python"))
        except Exception as e:
            agent.execution_log.append(f"Direct scaffold write error: {e}")
        created_files = _collect()

    agent.files_created = created_files
    agent.execution_log.append(f"Scaffold created {len(created_files)} files")
    return len(created_files) > 0


def _write_execution_log(project_id: str, log_entry: str):
    """Write execution log entry to file"""
    try:
        log_file = PROJECTS_DIR / project_id / "execution.log"
        with open(log_file, "a", encoding="utf-8") as f:
            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            f.write(f"[{timestamp}] {log_entry}\n")
    except Exception:
        pass  # Don't fail if logging fails


async def enhance_prompt(agent: AgentProject, agents: dict) -> bool:
    """Enhance the user prompt with comprehensive requirements"""
    try:
        agent.status = ProjectStatus.ENHANCING
        agent.updated_at = datetime.now()
        _write_execution_log(agent.project_id, "Starting prompt enhancement")

        # Try to enhance the prompt using LLM
        try:
            logger.info(f"Enhancing prompt for project {agent.project_id}")
            enhanced_response = agents["prompt_enhancer"].generate_reply(
                messages=[{"role": "user", "content": agent.prompt}]
            )
            enhanced_content = (
                enhanced_response.get("content", "")
                if isinstance(enhanced_response, dict)
                else str(enhanced_response)
            )
            logger.info(f"Prompt enhancement completed for project {agent.project_id}")

            # Extract enhanced prompt from response
            if "## ENHANCED PROMPT:" in enhanced_content:
                enhanced_prompt = enhanced_content.split("## ENHANCED PROMPT:")[
                    -1
                ].strip()
                agent.enhanced_prompt = enhanced_prompt
                agent.execution_log.append("Prompt enhanced successfully")
                _write_execution_log(agent.project_id, "Prompt enhanced successfully")
            else:
                # Fallback: use original prompt
                agent.enhanced_prompt = agent.prompt
                agent.execution_log.append("Prompt enhancement failed, using original")
                _write_execution_log(
                    agent.project_id, "Prompt enhancement failed, using original"
                )
        except Exception as e:
            # Fallback: use original prompt
            agent.enhanced_prompt = agent.prompt
            agent.execution_log.append(
                f"Prompt enhancement failed: {e}, using original"
            )
            _write_execution_log(
                agent.project_id, f"Prompt enhancement failed: {e}, using original"
            )

        return True
    except Exception as e:
        agent.status = ProjectStatus.FAILED
        agent.execution_log.append(f"Prompt enhancement failed: {e}")
        _write_execution_log(agent.project_id, f"Prompt enhancement failed: {e}")
        return False


async def generate_code(agent: AgentProject, agents: dict) -> bool:
    """Generate complete code implementation into a per-project sandbox.
    This uses the same robust approach as /debug/two-agent to guarantee files.
    """
    try:
        agent.status = ProjectStatus.GENERATING
        agent.updated_at = datetime.now()
        _write_execution_log(agent.project_id, "Starting code generation")

        project_dir = PROJECTS_DIR / agent.project_id
        project_dir.mkdir(parents=True, exist_ok=True)

        # Create instruction files for the agent to read
        _write_instruction_files(project_dir, agent)

        # Generate file plan first
        logger.info(f"Generating file plan for project {agent.project_id}")
        file_plan_response = agents["file_planner"].generate_reply(
            messages=[
                {
                    "role": "user",
                    "content": f"Create a file plan for: {agent.enhanced_prompt}",
                }
            ]
        )
        file_plan_content = (
            file_plan_response.get("content", "")
            if isinstance(file_plan_response, dict)
            else str(file_plan_response)
        )
        logger.info(f"File plan generated for project {agent.project_id}")

        # Save the plan
        plan_file = project_dir / "__plan__.json"
        try:
            # Try to extract JSON from response
            if "```json" in file_plan_content:
                json_start = file_plan_content.find("```json") + 7
                json_end = file_plan_content.find("```", json_start)
                json_content = file_plan_content[json_start:json_end].strip()
                plan_file.write_text(json_content, encoding="utf-8")
            else:
                # Fallback: create a simple plan
                simple_plan = {
                    "files": [
                        {"name": "README.md", "content": "Project documentation"},
                        {"name": "app.py", "content": "Main application file"},
                    ]
                }
                plan_file.write_text(
                    json.dumps(simple_plan, indent=2), encoding="utf-8"
                )
        except Exception as e:
            agent.execution_log.append(f"Plan creation failed: {e}")

        # Generate code using the plan
        logger.info(f"Generating code for project {agent.project_id}")
        code_response = agents["code_generator"].generate_reply(
            messages=[
                {
                    "role": "user",
                    "content": f"Generate code for: {agent.enhanced_prompt}",
                }
            ]
        )
        code_content = (
            code_response.get("content", "")
            if isinstance(code_response, dict)
            else str(code_response)
        )
        logger.info(f"Code generation completed for project {agent.project_id}")

        # Extract and execute Python code
        code_block = extract_python_code_block(code_content)
        print(f"[DEBUG] Extracted code block: {code_block[:200]}...")
        if code_block:
            # Prepare execution environment
            preamble = (
                f"import os\n"
                f"project_dir = r'{project_dir.as_posix()}'\n"
                f"os.makedirs(project_dir, exist_ok=True)\n"
            )
            snippet = preamble + "\n" + code_block
            print(f"[DEBUG] Executing snippet: {snippet[:300]}...")
            result = run_python_snippet_in_dir(snippet, cwd=project_dir)
            print(f"[DEBUG] Execution result: {result}")

            # Collect created files (exclude venv but include actual project files)
            created_files = []
            if project_dir.exists():
                for file_path in project_dir.rglob("*"):
                    if file_path.is_file():
                        # Skip venv files but include actual project files
                        rel_path = str(file_path.relative_to(WORKSPACE_DIR))
                        if not any(
                            part in rel_path
                            for part in [".venv", "__pycache__", "site-packages", "pip"]
                        ):
                            # Include files from nested directories (actual project files)
                            created_files.append(rel_path)

            agent.files_created = created_files
            agent.execution_log.append(
                f"Code generation completed, created {len(created_files)} files"
            )
            _write_execution_log(
                agent.project_id,
                f"Code generation completed, created {len(created_files)} files",
            )
            return len(created_files) > 0
        else:
            agent.execution_log.append("No code block found in response")
            _write_execution_log(agent.project_id, "No code block found in response")
            return False

    except Exception as e:
        agent.status = ProjectStatus.FAILED
        agent.execution_log.append(f"Code generation failed: {e}")
        _write_execution_log(agent.project_id, f"Code generation failed: {e}")
        return False


def _write_instruction_files(project_dir: Path, agent: AgentProject):
    """Write instruction files for agents to read"""
    # CONTEXT.md
    context_file = project_dir / "CONTEXT.md"
    context_file.write_text(
        f"""# Project Context

## Original Prompt
{agent.prompt}

## Enhanced Prompt
{agent.enhanced_prompt}

## Language
{getattr(agent, 'language', 'python')}

## Guidance Level
{getattr(agent, 'guidance', 'standard')}
""",
        encoding="utf-8",
    )

    # CONSTRAINTS.md
    constraints_file = project_dir / "CONSTRAINTS.md"
    constraints_file.write_text(
        """# Project Constraints

- Use only standard library modules when possible
- Ensure code is production-ready with error handling
- Include proper documentation and comments
- Follow best practices for the selected language
- Create a complete, runnable project
""",
        encoding="utf-8",
    )

    # PLANNING_GUIDE.md
    planning_file = project_dir / "PLANNING_GUIDE.md"
    planning_file.write_text(
        """# Planning Guide

## File Structure Planning
1. Identify the main application file
2. Create necessary configuration files
3. Add documentation (README.md)
4. Include test files if appropriate
5. Add dependency files (requirements.txt, package.json, etc.)

## Implementation Steps
1. Start with core functionality
2. Add error handling and validation
3. Include proper documentation
4. Ensure the project can be run immediately
""",
        encoding="utf-8",
    )


async def execute_code(agent: AgentProject, agents: dict) -> bool:
    """Execute the generated code"""
    try:
        agent.status = ProjectStatus.INSTALLING
        agent.updated_at = datetime.now()

        project_dir = PROJECTS_DIR / agent.project_id

        # Set up language-specific environment
        language = getattr(agent, "language", "python").lower()
        if language == "python":
            ensure_python_venv(project_dir)

        agent.status = ProjectStatus.EXECUTING
        agent.updated_at = datetime.now()

        # Try to run the main file
        main_file = _find_main_file(project_dir, language)
        if main_file:
            result = run_command([main_file], cwd=project_dir, timeout_sec=30)
            agent.execution_log.append(f"Execution result: {result.get('stdout', '')}")
            return result.get("returncode", 1) == 0

        return True
    except Exception as e:
        agent.status = ProjectStatus.FAILED
        agent.execution_log.append(f"Code execution failed: {e}")
        return False


def _find_main_file(project_dir: Path, language: str) -> Optional[str]:
    """Find the main executable file for a project"""
    if language == "python":
        for pattern in ["app.py", "main.py", "run.py"]:
            if (project_dir / pattern).exists():
                return f"python {pattern}"
    elif language in ("node", "javascript"):
        if (project_dir / "package.json").exists():
            return "npm start"
        elif (project_dir / "index.js").exists():
            return "node index.js"
    elif language in ("react-ts", "react"):
        if (project_dir / "package.json").exists():
            return "npm start"
    return None


async def run_tests(agent: AgentProject, agents: dict) -> bool:
    """Run tests on the implementation"""
    try:
        agent.status = ProjectStatus.TESTING
        agent.updated_at = datetime.now()

        project_dir = PROJECTS_DIR / agent.project_id
        language = getattr(agent, "language", "python").lower()

        # Run language-specific tests
        if language == "python" and (project_dir / "test_*.py").exists():
            result = run_command(
                ["python", "-m", "pytest"], cwd=project_dir, timeout_sec=60
            )
            agent.execution_log.append(f"Tests completed: {result.get('stdout', '')}")

        return True
    except Exception as e:
        agent.status = ProjectStatus.FAILED
        agent.execution_log.append(f"Testing failed: {e}")
        return False


async def deploy_code(agent: AgentProject, agents: dict) -> bool:
    """Package and deploy the implementation"""
    try:
        agent.status = ProjectStatus.DEPLOYING
        agent.updated_at = datetime.now()

        project_dir = PROJECTS_DIR / agent.project_id

        # Create deployment package
        agent.execution_log.append("Project packaged for deployment")

        return True
    except Exception as e:
        agent.status = ProjectStatus.FAILED
        agent.execution_log.append(f"Deployment failed: {e}")
        return False


async def process_project_full_pipeline(project_id: str):
    """Process a project through the complete pipeline"""
    if project_id not in projects:
        return

    agent = projects[project_id]

    # Import agents dynamically to avoid circular imports
    from agents.factory import create_agents

    agents = create_agents()

    # Execute pipeline steps
    pipeline_steps = [
        ("Prompt Enhancement", enhance_prompt),
        ("Code Generation", generate_code),
        ("Code Execution", execute_code),
        ("Testing", run_tests),
        ("Deployment", deploy_code),
    ]

    for step_name, step_func in pipeline_steps:
        print(f"[Pipeline] Executing step: {step_name}")
        success = await step_func(agent, agents)
        print(f"[Pipeline] Step {step_name} result: {success}")

        if not success:
            # Error recovery: if code generation failed, force a minimal scaffold and continue
            if step_name == "Code Generation":
                try:
                    _ok = _force_minimal_scaffold(agent)
                    if _ok:
                        agent.execution_log.append(
                            "Recovery: Minimal scaffold created after generation failure. Continuing pipeline."
                        )
                        continue
                except Exception as rec_e:
                    agent.execution_log.append(f"Recovery attempt failed: {rec_e}")
            break

    if agent.status != ProjectStatus.FAILED:
        agent.status = ProjectStatus.COMPLETED
        agent.updated_at = datetime.now()
