#!/usr/bin/env python3
"""
FastAPI server for full-text search.
Provides REST API endpoints for search and document retrieval with async support.
"""

import argparse
import asyncio
import sys
from concurrent.futures import ThreadPoolExecutor
from typing import Any

import uvicorn
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware

from ..core import (
    IndexConfig,
    SearchRequest,
    FetchRequest,
    BatchRequest,
    SearchResponse,
    FetchResponse,
    BatchResponse,
    IndicesResponse,
    HealthResponse,
    load_config_from_yaml,
)
from ..service import SearchService, SearchServiceConfig


class SearchAPI:
    """FastAPI server for full-text search with async support.

    Wraps SearchService with async HTTP endpoints.
    """

    def __init__(
        self,
        server_name: str,
        server_description: str,
        indices: list[IndexConfig] | None = None,
        max_workers: int = 4,
    ):
        """Initialize the search API server.

        Args:
            server_name: Name of the server
            server_description: Description of the server
            indices: List of index configurations
            max_workers: Number of worker threads for search operations
        """
        self.server_name = server_name
        self.server_description = server_description
        self.executor = ThreadPoolExecutor(max_workers=max_workers)

        # Initialize the core search service
        self._service = SearchService(
            SearchServiceConfig(
                server_name=server_name,
                server_description=server_description,
            )
        )

        # Initialize indices if provided
        if indices:
            for index_config in indices:
                self._service.add_index(index_config)

        # Create FastAPI app
        self.app = self._create_app()

    # Expose service properties for compatibility with mcp_plus_api
    @property
    def indices(self) -> dict:
        """Access to underlying service indices."""
        return self._service.indices

    @indices.setter
    def indices(self, value: dict) -> None:
        """Set underlying service indices."""
        self._service.indices = value

    @property
    def citations(self) -> dict:
        """Access to underlying service citations."""
        return self._service.citations

    @citations.setter
    def citations(self, value: dict) -> None:
        """Set underlying service citations."""
        self._service.citations = value

    def add_index(self, config: IndexConfig) -> None:
        """Add and initialize a single index."""
        self._service.add_index(config)

    async def _search_async(
        self,
        index: str,
        queries: list[str],
        limit: int = 5,
        include_content: bool = False,
        metadata_filters: dict[str, list[str]] | None = None,
    ) -> SearchResponse:
        """Async wrapper for search operation."""
        loop = asyncio.get_event_loop()

        def _search():
            return self._service.search(
                index, queries, limit, include_content, metadata_filters
            )

        try:
            return await loop.run_in_executor(self.executor, _search)
        except ValueError as e:
            raise HTTPException(status_code=404, detail=str(e))

    async def _fetch_async(
        self,
        index: str,
        document_ids: list[str],
        filters: list[dict[str, Any]] | None = None,
    ) -> FetchResponse:
        """Async wrapper for document fetch operation."""
        loop = asyncio.get_event_loop()

        def _fetch():
            return self._service.fetch(index, document_ids, filters)

        try:
            return await loop.run_in_executor(self.executor, _fetch)
        except ValueError as e:
            raise HTTPException(status_code=404, detail=str(e))

    def _create_app(self) -> FastAPI:
        """Create and configure FastAPI application."""
        app = FastAPI(
            title=self.server_name,
            description=self.server_description,
            version="1.0.0",
        )

        # Add CORS middleware
        app.add_middleware(
            CORSMiddleware,
            allow_origins=["*"],
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],
        )

        # Health check endpoint
        @app.get("/health", response_model=HealthResponse)
        async def health_check():
            return self._service.health_check()

        # List indices endpoint
        @app.get("/indices", response_model=IndicesResponse)
        async def list_indices():
            return self._service.list_indices()

        # Search endpoint
        @app.post("/search", response_model=SearchResponse)
        async def search(request: SearchRequest):
            try:
                return await self._search_async(
                    request.index,
                    request.queries,
                    request.limit,
                    request.include_content,
                    request.metadata_filters,
                )
            except HTTPException:
                raise
            except Exception as e:
                raise HTTPException(status_code=500, detail=str(e))

        # Fetch endpoint
        @app.post("/fetch", response_model=FetchResponse)
        async def fetch(request: FetchRequest):
            try:
                return await self._fetch_async(
                    request.index,
                    request.document_ids,
                    request.filters,
                )
            except HTTPException:
                raise
            except Exception as e:
                raise HTTPException(status_code=500, detail=str(e))

        # Batch endpoint
        @app.post("/batch", response_model=BatchResponse)
        async def batch(request: BatchRequest):
            try:
                # Execute all commands concurrently
                tasks = []
                for command in request.commands:
                    if command.name == "search":
                        if not command.queries:

                            async def error_task():
                                return {"error": "queries required for search"}

                            task = error_task()
                        else:
                            task = self._search_async(
                                command.index,
                                command.queries,
                                command.limit,
                                command.include_content,
                                command.metadata_filters,
                            )
                    elif command.name == "fetch":
                        if not command.document_ids:

                            async def error_task():
                                return {"error": "document_ids required for fetch"}

                            task = error_task()
                        else:
                            task = self._fetch_async(
                                command.index,
                                command.document_ids,
                                command.filters,
                            )
                    else:

                        async def error_task(cmd_name=command.name):
                            return {"error": f"Unknown command: {cmd_name}"}

                        task = error_task()
                    tasks.append(task)

                # Wait for all tasks to complete
                results = await asyncio.gather(*tasks, return_exceptions=True)

                # Convert results and exceptions to dictionaries
                formatted_results = []
                for result in results:
                    if isinstance(result, Exception):
                        formatted_results.append({"error": str(result)})
                    elif hasattr(result, "model_dump"):
                        formatted_results.append(result.model_dump())
                    else:
                        formatted_results.append(result)

                return BatchResponse(results=formatted_results)
            except Exception as e:
                raise HTTPException(status_code=500, detail=str(e))

        # Citation endpoint
        @app.get("/citation/{index}/{doc_id}")
        async def get_citation(index: str, doc_id: str):
            result = self._service.get_citation(index, doc_id)
            if result is not None:
                return result
            else:
                raise HTTPException(status_code=404, detail="Citation not found")

        return app

    async def shutdown(self):
        """Cleanup resources."""
        self.executor.shutdown(wait=True)

    def run(self, host: str = "0.0.0.0", port: int = 8000, **kwargs):
        """Run the FastAPI server.

        Args:
            host: Host to bind to
            port: Port to bind to
            **kwargs: Additional uvicorn configuration
        """
        print(f"Starting search API server on {host}:{port}")
        print(f"Indices: {list(self._service.indices.keys())}")
        print(f"API docs: http://{host}:{port}/docs")

        uvicorn.run(self.app, host=host, port=port, **kwargs)


def main():
    """CLI entry point for running the search API server."""
    parser = argparse.ArgumentParser(description="Full-text search API server")

    # Add config file option
    parser.add_argument(
        "--config",
        required=True,
        help="Path to YAML configuration file",
    )

    # Optional arguments
    parser.add_argument("--host", default="0.0.0.0", help="Host for HTTP server")
    parser.add_argument("--port", type=int, default=8000, help="Port for HTTP server")
    parser.add_argument(
        "--workers",
        type=int,
        default=4,
        help="Number of worker threads for search operations",
    )
    parser.add_argument(
        "--reload", action="store_true", help="Enable auto-reload for development"
    )

    args = parser.parse_args()

    try:
        # Load configuration from YAML file
        server_name, server_description, index_configs, server_config = (
            load_config_from_yaml(args.config)
        )

        # Create search API server
        api_server = SearchAPI(
            server_name=server_name,
            server_description=server_description,
            indices=index_configs,
            max_workers=args.workers,
        )

        # Use host/port from YAML config if specified, otherwise use CLI args
        host = server_config.get("host", args.host)
        port = server_config.get("port", args.port)

        # Start server
        api_server.run(host=host, port=port, reload=args.reload)

    except Exception as e:
        print(f"Error starting server: {e}", file=sys.stderr)
        return 1

    return 0


if __name__ == "__main__":
    sys.exit(main())
