from __future__ import annotations

import re
from typing import Any

from app.modules.agent.runtime.docs_qa_pipeline.models import DocsEvidenceBundle


class OpenAPIEvidenceExtractor:
    _PATH_RE = re.compile(r"(/[a-z0-9_./{}-]+)", re.IGNORECASE)
    _METHOD_RE = re.compile(r"\b(get|post|put|patch|delete)\b", re.IGNORECASE)
    _FIELD_RE = re.compile(r"\b([a-z_][a-z0-9_]{1,40})\b")
    _STATUS_RE = re.compile(r"\b(200|201|202|204|400|401|403|404|409|422|500|503)\b")
    _FIELD_STOPWORDS = {
        "request",
        "response",
        "schema",
        "payload",
        "fields",
        "contains",
        "type",
        "properties",
        "post",
        "get",
        "put",
        "patch",
        "delete",
        "endpoint",
        "returns",
        "return",
        "with",
        "and",
        "for",
        "send",
        "health",
        "status",
        "result",
        "body",
    }

    def extract(self, bundle: DocsEvidenceBundle, *, requested_fragment_type: str | None) -> dict[str, Any]:
        items = [*bundle.facts, *bundle.documents, *bundle.chunks]
        path = self._extract_path(items)
        method = self._extract_method(items)
        request_schema = self._extract_schema(items, "request")
        response_schema = self._extract_schema(items, "response")
        if request_schema is None and requested_fragment_type == "request_schema":
            request_schema = self._schema_from_text(items, kind="request")
        if response_schema is None and requested_fragment_type == "response_schema":
            response_schema = self._schema_from_text(items, kind="response")
        if request_schema is None and response_schema is None and requested_fragment_type == "schema_fragment":
            request_schema = self._schema_from_text(items, kind="schema")
        status_codes = self._status_codes(items)
        content_type = self._content_type(items)
        operation_summary = self._operation_summary(items)
        examples_found = self._examples_found(items)
        diagnostics = {
            "has_path": bool(path),
            "has_method": bool(method),
            "has_request": request_schema is not None,
            "has_response": response_schema is not None,
            "operation_semantics_found": bool(operation_summary),
            "request_payload_found": request_schema is not None or self._has_payload_text(items, "request"),
            "request_fields_found": self._field_count(request_schema) > 0,
            "response_payload_found": response_schema is not None or self._has_payload_text(items, "response"),
            "response_fields_found": self._field_count(response_schema) > 0,
            "status_codes_found": bool(status_codes),
            "content_type_found": bool(content_type),
            "examples_found": examples_found,
            "payload_description_found": self._has_payload_description(items),
            "status_codes": status_codes,
            "content_type": content_type,
            "operation_summary": operation_summary,
        }
        return {
            "path": path,
            "method": method,
            "request_schema": request_schema,
            "response_schema": response_schema,
            "diagnostics": diagnostics,
        }

    def _extract_path(self, items: list[dict[str, Any]]) -> str:
        for item in items:
            metadata = dict(item.get("metadata") or {})
            if metadata.get("endpoint"):
                return str(metadata["endpoint"])
            for source in (item.get("title"), item.get("content")):
                match = self._PATH_RE.search(str(source or ""))
                if match:
                    return match.group(1)
        return ""

    def _extract_method(self, items: list[dict[str, Any]]) -> str:
        for item in items:
            metadata = dict(item.get("metadata") or {})
            if metadata.get("http_method"):
                return str(metadata["http_method"]).lower()
            for source in (item.get("title"), item.get("content")):
                match = self._METHOD_RE.search(str(source or ""))
                if match:
                    return match.group(1).lower()
        return ""

    def _extract_schema(self, items: list[dict[str, Any]], kind: str) -> dict[str, Any] | None:
        for item in items:
            metadata = dict(item.get("metadata") or {})
            candidate = metadata.get(f"{kind}_schema") or metadata.get(f"{kind}_fields")
            schema = self._as_schema(candidate)
            if schema is not None:
                return schema
        return None

    def _schema_from_text(self, items: list[dict[str, Any]], *, kind: str) -> dict[str, Any] | None:
        markers = {
            "request": ("request", "payload", "body", "fields", "message", "chat_id"),
            "response": ("response", "returns", "result", "status", "body"),
            "schema": ("schema", "payload", "fields", "properties"),
        }
        fields: list[str] = []
        for item in items:
            text = " ".join((str(item.get("title") or ""), str(item.get("content") or ""))).lower()
            if not any(marker in text for marker in markers[kind]):
                continue
            for token in self._FIELD_RE.findall(text):
                if token in self._FIELD_STOPWORDS:
                    continue
                if token not in fields:
                    fields.append(token)
        if not fields:
            return None
        properties = {name: {"type": "string"} for name in fields[:8]}
        required = [name for name in fields[:3] if len(name) > 1]
        result: dict[str, Any] = {"type": "object", "properties": properties}
        if required:
            result["required"] = required
        return result

    def _as_schema(self, value: Any) -> dict[str, Any] | None:
        if isinstance(value, dict):
            return value
        if isinstance(value, list):
            properties = {str(item): {"type": "string"} for item in value if str(item).strip()}
            return {"type": "object", "properties": properties} if properties else None
        return None

    def _status_codes(self, items: list[dict[str, Any]]) -> list[str]:
        values: list[str] = []
        for item in items:
            text = " ".join((str(item.get("title") or ""), str(item.get("content") or "")))
            for code in self._STATUS_RE.findall(text):
                if code not in values:
                    values.append(code)
        return values

    def _content_type(self, items: list[dict[str, Any]]) -> str:
        for item in items:
            text = " ".join((str(item.get("title") or ""), str(item.get("content") or ""))).lower()
            for marker in ("application/json", "json", "multipart/form-data", "x-www-form-urlencoded"):
                if marker in text:
                    return marker
        return ""

    def _operation_summary(self, items: list[dict[str, Any]]) -> str:
        for item in items:
            for source in (item.get("title"), item.get("content")):
                text = str(source or "").strip()
                if len(text) >= 8:
                    return text[:120]
        return ""

    def _examples_found(self, items: list[dict[str, Any]]) -> bool:
        for item in items:
            text = " ".join((str(item.get("title") or ""), str(item.get("content") or ""))).lower()
            if any(marker in text for marker in ("example", "пример", "{", "}")):
                return True
        return False

    def _has_payload_text(self, items: list[dict[str, Any]], kind: str) -> bool:
        markers = ("payload", "body", "fields", "request") if kind == "request" else ("response", "returns", "status", "body")
        return any(any(marker in str(item.get("content") or "").lower() for marker in markers) for item in items)

    def _has_payload_description(self, items: list[dict[str, Any]]) -> bool:
        return any(len(str(item.get("content") or "").strip()) >= 12 for item in items)

    def _field_count(self, schema: dict[str, Any] | None) -> int:
        return len((schema or {}).get("properties") or {})
