"""Load OpenAPI spec (URL or path) into SaaSModel."""

import json
from pathlib import Path

import httpx
import yaml

from foremet.ingest.model import AuthConfig, AuthType, Operation, Resource, SaaSModel


def _load_spec_raw(source: str, is_url: bool) -> dict:
    """Load OpenAPI spec as dict. source is URL or local path."""
    resp = None
    if is_url:
        resp = httpx.get(source, follow_redirects=True)
        resp.raise_for_status()
        text = resp.text
    else:
        path = Path(source)
        if not path.exists():
            raise FileNotFoundError(f"OpenAPI file not found: {source}")
        text = path.read_text()

    if source.endswith(".json"):
        return json.loads(text)
    if is_url and resp is not None and "json" in (resp.headers.get("content-type") or ""):
        return json.loads(text)
    return yaml.safe_load(text)


def _parse_parameters(op: dict, path: str) -> list[dict]:
    """Extract parameter list for an operation."""
    params = []
    for p in op.get("parameters") or []:
        name = p.get("name")
        if not name:
            continue
        params.append({
            "name": name,
            "in": p.get("in", "query"),
            "required": p.get("required", False),
            "schema": p.get("schema") or {},
            "description": p.get("description"),
        })
    return params


def _parse_request_body(op: dict) -> dict | None:
    """Get request body schema if present."""
    rb = op.get("requestBody")
    if not rb:
        return None
    content = rb.get("content") or {}
    for ct in ("application/json", "application/x-www-form-urlencoded"):
        if ct in content:
            return content[ct].get("schema") or {}
    if content:
        first = next(iter(content.values()))
        return first.get("schema") or {}
    return None


def _path_to_resource(path: str) -> str:
    """Derive resource name from path (e.g. /pets -> pets, /v1/users -> users)."""
    parts = [p for p in path.split("/") if p and not p.startswith("{")]
    return parts[-1] if parts else "default"


def _parse_spec(spec: dict) -> SaaSModel:
    """Convert OpenAPI spec dict to SaaSModel."""
    title = spec.get("info", {}).get("title")
    description = spec.get("info", {}).get("description")
    paths = spec.get("paths") or {}
    tag_descriptions: dict[str, str] = {}
    for tag in spec.get("tags") or []:
        if isinstance(tag, dict) and tag.get("name"):
            tag_descriptions[tag["name"]] = tag.get("description", "")

    resources_by_name: dict[str, Resource] = {}
    operations: list[Operation] = []

    for path_str, path_item in paths.items():
        if not isinstance(path_item, dict):
            continue
        for method in ("get", "post", "put", "patch", "delete", "head", "options"):
            op_spec = path_item.get(method)
            if not isinstance(op_spec, dict):
                continue
            op_id = op_spec.get("operationId") or f"{method}_{path_str.replace('/', '_').strip('_')}"
            tags = op_spec.get("tags") or []
            resource_name = tags[0] if tags else _path_to_resource(path_str)
            if resource_name not in resources_by_name:
                resources_by_name[resource_name] = Resource(
                    name=resource_name,
                    description=tag_descriptions.get(resource_name),
                )
            params = _parse_parameters(op_spec, path_str)
            body_schema = _parse_request_body(op_spec)
            operations.append(Operation(
                method=method.upper(),
                path=path_str,
                operation_id=op_id,
                summary=op_spec.get("summary"),
                description=op_spec.get("description"),
                tags=tags,
                parameters=params,
                request_body_schema=body_schema,
            ))

    auth: AuthConfig | None = None
    sec = spec.get("components", {}).get("securitySchemes") or {}
    if sec:
        for name, scheme in sec.items():
            if isinstance(scheme, dict):
                stype = (scheme.get("type") or "").lower()
                if stype == "http" and (scheme.get("scheme") or "").lower() == "bearer":
                    auth = AuthConfig(type=AuthType.bearer, env_var="API_KEY")
                    break
                if stype == "apikey":
                    auth = AuthConfig(type=AuthType.api_key, env_var=scheme.get("name") or "API_KEY")
                    break

    return SaaSModel(
        title=title,
        description=description,
        resources=list(resources_by_name.values()),
        operations=operations,
        auth=auth,
    )


def load_openapi_to_model(source: str, is_url: bool | None = None) -> SaaSModel:
    """Load OpenAPI from URL or path and return SaaSModel.
    If is_url is None, guess from source (starts with http or path exists).
    """
    if is_url is None:
        is_url = source.startswith("http://") or source.startswith("https://")
    raw = _load_spec_raw(source, is_url)
    return _parse_spec(raw)
