from abc import ABC, abstractmethod
from threading import Thread
from time import sleep

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from uvicorn import Config, Server

from yera.config.logging import LOG_CONFIG


class BaseServer(Server, ABC):
    def __init__(self, host: str | None = "127.0.0.1", port: int | None = 8989):
        self.host = host
        self.port = port
        self.thread = None
        app = self.build_api()
        app = self._add_middleware(app)

        # Expose the fully configured FastAPI app (including middleware such as CORS)
        # so that external runners (e.g. uvicorn infra_mvp.stream.app:app) can
        # reuse the same application instance.
        self.app: FastAPI = app

        super().__init__(Config(self.app, self.host, self.port, log_config=LOG_CONFIG))

    @property
    def url_base(self):
        return f"http://{self.host}:{self.port}"

    @abstractmethod
    def build_api(self) -> FastAPI:
        pass

    def _add_middleware(self, app: FastAPI) -> FastAPI:
        # Add CORS middleware for local development - enables Postman testing and future web frontends
        app.add_middleware(
            CORSMiddleware,
            allow_origins=["*"],  # Allow all origins for local development
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],
        )
        return app

    def start(self):
        self.thread = Thread(target=self.run)
        self.thread.start()
        while not self.started:
            if not self.thread.is_alive():
                raise RuntimeError("Server failed to start")
            sleep(0.1)

        # If port was 0 (random), update it with the actual bound port
        if self.port == 0:
            # Update port with the actual bound port from the first socket
            self.port = self.servers[0].sockets[0].getsockname()[1]

    def stop(self):
        self.should_exit = True
        self.thread.join()

    def __enter__(self) -> "BaseServer":
        self.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.stop()
