from __future__ import annotations

from typing import Any

from app.modules.agent.runtime.docs_qa_pipeline.models import DocsEvidenceBundle, OpenAPIResult
from app.modules.agent.runtime.docs_qa_pipeline.openapi_evidence_extractor import OpenAPIEvidenceExtractor


class OpenAPIGenerator:
    def __init__(self, extractor: OpenAPIEvidenceExtractor | None = None) -> None:
        self._extractor = extractor or OpenAPIEvidenceExtractor()

    def generate(
        self,
        evidence_bundle: DocsEvidenceBundle,
        mode: str,
        *,
        requested_fragment_type: str | None = None,
    ) -> OpenAPIResult:
        extracted = self._extractor.extract(evidence_bundle, requested_fragment_type=requested_fragment_type)
        path = str(extracted["path"] or "")
        method = str(extracted["method"] or "")
        request_schema = extracted["request_schema"]
        response_schema = extracted["response_schema"]
        diagnostics = dict(extracted["diagnostics"] or {})
        diagnostics["missing_required_fields"] = self._missing_fields(
            path=path,
            method=method,
            request_schema=request_schema,
            response_schema=response_schema,
            mode=mode,
            requested_fragment_type=requested_fragment_type,
            diagnostics=diagnostics,
        )
        raw_yaml = self._render(
            path=path,
            method=method,
            request_schema=request_schema,
            response_schema=response_schema,
            mode=mode,
            requested_fragment_type=requested_fragment_type,
            diagnostics=diagnostics,
        )
        return OpenAPIResult(
            path=path,
            method=method,
            request_schema=request_schema,
            response_schema=response_schema,
            raw_yaml=raw_yaml,
            diagnostics=diagnostics,
        )

    def _missing_fields(
        self,
        *,
        path: str,
        method: str,
        request_schema: dict[str, Any] | None,
        response_schema: dict[str, Any] | None,
        mode: str,
        requested_fragment_type: str | None,
        diagnostics: dict[str, Any],
    ) -> list[str]:
        missing: list[str] = []
        if not path:
            missing.append("path")
        if mode == "OPENAPI_FRAGMENT_GENERATE":
            if requested_fragment_type == "request_schema" and request_schema is None and not diagnostics.get("request_payload_found"):
                missing.append("request_schema")
            elif requested_fragment_type == "response_schema" and response_schema is None and not diagnostics.get("response_payload_found"):
                missing.append("response_schema")
            elif requested_fragment_type == "parameters" and not diagnostics.get("has_method"):
                missing.append("parameters")
            elif request_schema is None and response_schema is None and not diagnostics.get("payload_description_found"):
                missing.append("schema_fragment")
            return missing
        if not method:
            missing.append("method")
        if request_schema is None and not diagnostics.get("request_payload_found"):
            missing.append("request_schema")
        if response_schema is None and not diagnostics.get("response_payload_found") and not diagnostics.get("status_codes_found"):
            missing.append("response_schema")
        return missing

    def _render(
        self,
        *,
        path: str,
        method: str,
        request_schema: dict[str, Any] | None,
        response_schema: dict[str, Any] | None,
        mode: str,
        requested_fragment_type: str | None,
        diagnostics: dict[str, Any],
    ) -> str:
        if mode == "OPENAPI_FRAGMENT_GENERATE":
            if requested_fragment_type == "response_schema":
                return self._render_schema(response_schema, diagnostics, "Documented response fragment")
            return self._render_schema(request_schema or response_schema, diagnostics, "Documented schema fragment")
        if not path:
            return ""
        method_line = method or "get"
        summary = str(diagnostics.get("operation_summary") or "Documented API method")
        response_block = self._render_responses(response_schema, diagnostics)
        request_block = self._render_request_body(request_schema, diagnostics)
        return "\n".join(
            [
                "paths:",
                f"  {path}:",
                f"    {method_line}:",
                f"      summary: \"{summary}\"",
                *request_block,
                "      responses:",
                *response_block,
            ]
        )

    def _render_schema(self, schema: dict[str, Any] | None, diagnostics: dict[str, Any], fallback_description: str) -> str:
        if schema is None:
            return "\n".join(["type: object", f"description: \"{fallback_description}\""])
        return self._yaml_from_object(schema, indent=0)

    def _render_request_body(self, schema: dict[str, Any] | None, diagnostics: dict[str, Any]) -> list[str]:
        lines = ["      requestBody:"]
        if schema is None:
            description = "Documented request payload" if diagnostics.get("request_payload_found") else "Request payload not fully documented"
            lines.append(f"        description: \"{description}\"")
            return lines
        lines.append("        content:")
        content_type = str(diagnostics.get("content_type") or "application/json")
        lines.append(f"          {content_type}:")
        lines.append("            schema:")
        lines.extend(self._yaml_from_object(schema, indent=14).splitlines())
        return lines

    def _render_responses(self, schema: dict[str, Any] | None, diagnostics: dict[str, Any]) -> list[str]:
        status_codes = list(diagnostics.get("status_codes") or []) or ["200"]
        code = status_codes[0]
        lines = [f"        \"{code}\":", "          description: \"Documented response\""]
        if schema is not None:
            lines.append("          content:")
            content_type = str(diagnostics.get("content_type") or "application/json")
            lines.append(f"            {content_type}:")
            lines.append("              schema:")
            lines.extend(self._yaml_from_object(schema, indent=16).splitlines())
        return lines

    def _yaml_from_object(self, value: dict[str, Any], *, indent: int) -> str:
        lines: list[str] = []
        prefix = " " * indent
        for key, item in value.items():
            if isinstance(item, dict):
                lines.append(f"{prefix}{key}:")
                lines.append(self._yaml_from_object(item, indent=indent + 2))
            elif isinstance(item, list):
                lines.append(f"{prefix}{key}:")
                for row in item:
                    lines.append(f"{prefix}  - {row}")
            else:
                lines.append(f"{prefix}{key}: {item}")
        return "\n".join(lines)
