"""
Pipeline — orchestrates all 10 agents in sequence.

Planner → Architect → CodeGen → LLM-Validator → Tester
→ Backend → Packager → Git Init → TypeScript Verifier
→ Vuln Audit → Screenshotter → Cost Report
"""
from __future__ import annotations

import asyncio
import json
import time
from pathlib import Path
from typing import Optional, Callable, Literal

from forge.core.session import GenerationSession, LogEntry, AgentRun
from forge.core.api import GroqClient, NvidiaClient
from forge.core.cost import CostReport, _patch_groq_client, _patch_nvidia_client
from forge.core.git import forge_init, git_available
from forge.agents.planner import run_planner
from forge.agents.architect import run_architect
from forge.agents.codegen import run_codegen
from forge.agents.post import run_validator, run_tester, run_packager
from forge.agents.backend import run_backend_generator
from forge.agents.verifier import run_verifier
from forge.agents.auditor import run_audit
from forge.agents.screenshotter import run_screenshotter
from forge.agents.refiner import run_refine, RefineResult


class Pipeline:
    """
    10-agent autonomous pipeline.
    Usage:
        pipeline = Pipeline(settings)
        session = await pipeline.run("My app idea")
        session = await pipeline.refine(project_dir, "Add push notifications")
    """

    def __init__(self, settings):
        self.settings = settings
        self._groq: Optional[GroqClient] = None
        self._nvidia: Optional[NvidiaClient] = None
        self._ollama = None
        self.cost = CostReport()

    def _init_clients(self) -> None:
        backend = getattr(self.settings, "backend", "cloud")

        if backend == "ollama":
            from forge.core.ollama import OllamaClient
            host = getattr(self.settings, "ollama_host", "http://localhost:11434")
            model = getattr(self.settings, "ollama_model", "codellama:70b")
            self._ollama = OllamaClient(host=host, default_model=model)
            # Use Ollama as both groq-equivalent and nvidia-equivalent
            self._groq = self._ollama      # type: ignore[assignment]
            self._nvidia = self._ollama    # type: ignore[assignment]
        else:
            self._groq = GroqClient(
                self.settings.groq_api_key,
                timeout=self.settings.request_timeout,
            )
            self._nvidia = NvidiaClient(
                self.settings.nvidia_api_key,
                timeout=self.settings.request_timeout,
            )
            # Instrument for cost tracking
            _patch_groq_client(self._groq, self.cost)
            _patch_nvidia_client(self._nvidia, self.cost)

    async def run(
        self,
        prompt: str,
        clarification_answers: Optional[dict[str, str]] = None,
        on_log: Optional[Callable[[LogEntry], None]] = None,
        on_agent_update: Optional[Callable[[AgentRun], None]] = None,
        on_progress: Optional[Callable[[int, int, str], None]] = None,
    ) -> GenerationSession:
        """
        Run the full pipeline. Returns completed GenerationSession.
        """
        self._init_clients()
        self.cost = CostReport()  # reset per run

        session = GenerationSession(
            prompt=prompt,
            settings=self.settings,
            on_log=on_log,
            on_agent_update=on_agent_update,
            on_progress=on_progress,
        )

        if clarification_answers:
            session.clarification_answers = clarification_answers

        backend_mode = getattr(self.settings, "backend", "cloud")
        session.log_highlight("FORGE", f"━━━ SESSION {session.session_id} START ━━━")
        session.log("FORGE", f'Prompt: "{prompt}"')
        session.log("FORGE",
            f"Backend: {backend_mode} | "
            f"Complexity: {self.settings.complexity} | "
            f"Framework: {self.settings.framework}"
        )
        if backend_mode != "ollama":
            session.log("FORGE",
                f"Models: Groq/{self.settings.plan_model} "
                f"| NVIDIA/{self.settings.code_model}"
            )

        try:
            # ── Phase 1: Plan ─────────────────────────────────────────────
            session.log("FORGE", "Phase 1/10: PLANNING")
            await run_planner(session, self._groq)

            if session.needs_clarification and not clarification_answers:
                session.log("FORGE", "Pausing for clarification…", "warning")
                return session

            # ── Phase 2: Architecture ─────────────────────────────────────
            session.log("FORGE", "Phase 2/10: ARCHITECTURE")
            await run_architect(session, self._groq)

            # ── Phase 3: Code Generation ──────────────────────────────────
            session.log("FORGE", "Phase 3/10: CODE GENERATION")
            await run_codegen(session, self._groq, self._nvidia)

            # ── Phase 4: LLM Validation ───────────────────────────────────
            session.log("FORGE", "Phase 4/10: LLM VALIDATION")
            await run_validator(session, self._groq)

            # ── Phase 5: Test Generation ──────────────────────────────────
            session.log("FORGE", "Phase 5/10: TEST GENERATION")
            await run_tester(session, self._groq)

            # ── Phase 6: Backend ──────────────────────────────────────────
            cloud_backend = getattr(self.settings, "cloud_backend", None)
            if cloud_backend and cloud_backend != "none":
                session.log("FORGE", f"Phase 6/10: BACKEND ({cloud_backend.upper()})")
                await run_backend_generator(session, self._groq, backend=cloud_backend)
            else:
                session.log("FORGE", "Phase 6/10: BACKEND (skipped — local storage only)")

            # ── Phase 7: Package to disk ──────────────────────────────────
            session.log("FORGE", "Phase 7/10: PACKAGING")
            await run_packager(session)

            # ── Phase 8: Git init ─────────────────────────────────────────
            session.log("FORGE", "Phase 8/10: GIT INIT")
            if git_available() and session.project_dir:
                result = forge_init(
                    session.project_dir,
                    session.plan.app_name,
                    session.session_id,
                )
                if result.success:
                    session.log_success("FORGE", f"✓ git init + initial commit in {session.project_dir.name}/")
                else:
                    session.log("FORGE", f"⚠ git init failed: {result.error}", "warning")
            else:
                session.log("FORGE", "  git not available — skipping", "warning")

            # ── Phase 9: TypeScript Verification ─────────────────────────
            if not getattr(self.settings, "skip_ts_verify", False):
                session.log("FORGE", "Phase 9/10: TYPESCRIPT VERIFICATION")
                await run_verifier(session, self._groq)
            else:
                session.log("FORGE", "Phase 9/10: TS VERIFICATION (skipped)")

            # ── Phase 10: Vulnerability Audit ─────────────────────────────
            if not getattr(self.settings, "skip_audit", False):
                session.log("FORGE", "Phase 10/10: VULNERABILITY AUDIT")
                await run_audit(session, self._groq, auto_fix=True)
            else:
                session.log("FORGE", "Phase 10/10: AUDIT (skipped)")

            # ── Screenshots (optional, non-blocking) ─────────────────────
            if getattr(self.settings, "take_screenshots", False):
                session.log("FORGE", "BONUS: SCREENSHOTTER")
                screenshots = await run_screenshotter(session)
                session.screenshots = screenshots  # type: ignore[attr-defined]

            # ── Cost report ───────────────────────────────────────────────
            if backend_mode != "ollama" and self.cost.total_calls > 0:
                session.log_highlight("FORGE", "━━━ COST REPORT ━━━")
                for line in self.cost.summary_lines():
                    session.log("FORGE", line)
                # Save cost report
                if session.project_dir:
                    cost_path = session.project_dir / ".forge" / "cost.json"
                    cost_path.parent.mkdir(exist_ok=True)
                    self.cost.save(cost_path)

            elapsed = session.elapsed
            session.log_highlight(
                "FORGE",
                f"━━━ COMPLETE in {elapsed:.1f}s ━━━ "
                f"{session.total_files} files | {session.total_lines:,} lines | "
                f"${self.cost.total_cost_usd:.4f} total cost ━━━",
            )

        except Exception as e:
            session.log_error("FORGE", f"PIPELINE ERROR: {type(e).__name__}: {e}")
            raise

        return session

    async def refine(
        self,
        project_dir: Path,
        refine_request: str,
        session: Optional[GenerationSession] = None,
        on_log: Optional[Callable[[LogEntry], None]] = None,
        on_agent_update: Optional[Callable[[AgentRun], None]] = None,
    ) -> RefineResult:
        """
        Surgically refine an existing generated project.
        Loads plan from .forge/plan.json if no session provided.
        """
        self._init_clients()

        if session is None:
            # Reconstruct minimal session from disk
            from forge.core.session import AppPlan
            session = GenerationSession(
                prompt=refine_request,
                settings=self.settings,
                on_log=on_log,
                on_agent_update=on_agent_update,
            )
            session.project_dir = project_dir

            plan_path = project_dir / ".forge" / "plan.json"
            if plan_path.exists():
                session.plan = AppPlan.from_dict(json.loads(plan_path.read_text()))
        else:
            if on_log:
                session.on_log = on_log
            if on_agent_update:
                session.on_agent_update = on_agent_update

        result = await run_refine(
            project_dir=project_dir,
            refine_request=refine_request,
            session=session,
            groq=self._groq,
            nvidia=self._nvidia,
        )

        # Re-run TS verify on changed files
        if result.success and not getattr(self.settings, "skip_ts_verify", False):
            await run_verifier(session, self._groq)

        return result

    async def validate_keys(self) -> dict[str, bool]:
        """Test API keys by making minimal real requests."""
        results: dict[str, bool] = {}

        backend = getattr(self.settings, "backend", "cloud")

        if backend == "ollama":
            from forge.core.ollama import OllamaClient
            host = getattr(self.settings, "ollama_host", "http://localhost:11434")
            ollama = OllamaClient(host=host)
            results["ollama"] = await ollama.is_available()
            models = await ollama.list_models() if results["ollama"] else []
            results["ollama_models"] = len(models) > 0  # type: ignore[assignment]
            return results

        # Groq
        try:
            groq = GroqClient(self.settings.groq_api_key, timeout=15)
            resp = await groq.complete(
                messages=[{"role": "user", "content": "Say: OK"}],
                model=self.settings.plan_model,
                max_tokens=5,
            )
            results["groq"] = bool(resp)
        except Exception:
            results["groq"] = False

        # NVIDIA
        try:
            nvidia = NvidiaClient(self.settings.nvidia_api_key, timeout=20)
            code = await nvidia.generate_code(
                system_prompt="Output exactly: OK",
                user_prompt="Say: OK",
                model=self.settings.code_model,
                max_tokens=5,
            )
            results["nvidia"] = bool(code)
        except Exception:
            results["nvidia"] = False

        return results
