# Copyright 2025 Softwell S.r.l.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Endpoint discovery via OpenAPI schema."""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from .connection import WsxConnection

__all__ = ["EndpointDiscovery", "EndpointInfo"]

logger = logging.getLogger("genro_cli")


@dataclass
class EndpointInfo:
    """Parsed endpoint from OpenAPI schema."""

    path: str
    method: str
    summary: str = ""
    description: str = ""
    parameters: list[dict[str, Any]] = field(default_factory=list)
    request_body: dict[str, Any] | None = None
    tags: list[str] = field(default_factory=list)

    @property
    def display_name(self) -> str:
        """Short display: METHOD /path — summary."""
        label = f"{self.method.upper()} {self.path}"
        if self.summary:
            label += f" — {self.summary}"
        return label


class EndpointDiscovery:
    """Discovers server endpoints via OpenAPI schema over WSX."""

    __slots__ = ("connection", "_schema", "_endpoints")

    def __init__(self, connection: WsxConnection) -> None:
        self.connection = connection
        self._schema: dict[str, Any] | None = None
        self._endpoints: list[EndpointInfo] = []

    @property
    def endpoints(self) -> list[EndpointInfo]:
        return list(self._endpoints)

    @property
    def schema(self) -> dict[str, Any] | None:
        return self._schema

    async def discover(self) -> list[EndpointInfo]:
        """Fetch OpenAPI schema and parse into endpoint list."""
        response = await self.connection.request("GET", "/_server/openapi")
        if response.get("status") != 200:
            raise RuntimeError(
                f"OpenAPI fetch failed: status={response.get('status')}, "
                f"data={response.get('data')}"
            )

        self._schema = response.get("data", {})
        self._endpoints = self._parse_schema(self._schema)
        logger.info("Discovered %d endpoints", len(self._endpoints))
        return self._endpoints

    async def refresh(self) -> list[EndpointInfo]:
        """Re-fetch and parse the schema."""
        return await self.discover()

    def search(self, query: str) -> list[EndpointInfo]:
        """Filter endpoints by path or summary substring."""
        q = query.lower()
        return [
            ep for ep in self._endpoints
            if q in ep.path.lower() or q in ep.summary.lower()
        ]

    def get_endpoint(self, path: str, method: str = "GET") -> EndpointInfo | None:
        """Find a specific endpoint by path and method."""
        method_upper = method.upper()
        for ep in self._endpoints:
            if ep.path == path and ep.method == method_upper:
                return ep
        return None

    def _parse_schema(self, schema: dict[str, Any]) -> list[EndpointInfo]:
        """Extract EndpointInfo list from OpenAPI 3.x schema."""
        endpoints: list[EndpointInfo] = []
        paths = schema.get("paths", {})

        for path, methods in paths.items():
            if not isinstance(methods, dict):
                continue
            for method, operation in methods.items():
                if method.startswith("x-") or not isinstance(operation, dict):
                    continue
                endpoints.append(EndpointInfo(
                    path=path,
                    method=method.upper(),
                    summary=operation.get("summary", ""),
                    description=operation.get("description", ""),
                    parameters=operation.get("parameters", []),
                    request_body=operation.get("requestBody"),
                    tags=operation.get("tags", []),
                ))

        endpoints.sort(key=lambda ep: (ep.path, ep.method))
        return endpoints


if __name__ == "__main__":
    pass
