import asyncio
import logging
from contextlib import asynccontextmanager

from fastapi import FastAPI, Request

from nextpy_server.app import create_app as _base_create_app

from n6k_server.duckdb import register_duckdb
from n6k_server.routes import rpc as rpc_routes
from n6k_server.routes import tables, views, ws
from n6k_server.session import (
    SESSION_COOKIE,
    SESSION_HEADER,
    create_shared_db,
    destroy_session,
    reap_expired_sessions,
)
from n6k_server.table_registry import TableRegistry

_log = logging.getLogger("n6k_server")

DEFAULT_DATA_PREFIX = "/_n6k"


def create_app(*, data_prefix: str = DEFAULT_DATA_PREFIX, **kwargs) -> FastAPI:
    app = _base_create_app(**kwargs)

    app.state.data_prefix = data_prefix

    # Bolt on data layer state
    app.state.cached_tables = {}
    app.state.views = {}
    app.state.sessions = {}
    app.state.table_meta = {}
    app.state.table_registry = TableRegistry(app)

    # Replace the generic request logger with the session-aware one
    # Remove the last middleware (request_log from nextpy) and add ours
    app.middleware_stack = None  # force rebuild
    app.user_middleware = [m for m in app.user_middleware if m.kwargs.get("dispatch") is None]

    @app.middleware("http")
    async def session_log(request: Request, call_next):
        header_id = request.headers.get(SESSION_HEADER)
        cookie_id = request.cookies.get(SESSION_COOKIE)
        if header_id:
            sid = header_id[:8]
            via = "header"
        elif cookie_id:
            sid = cookie_id[:8]
            via = "cookie"
        else:
            sid = "shared"
            via = None
        response = await call_next(request)
        tag = f"session={sid} via={via}" if via else f"session={sid}"
        _log.info("%s %s %d [%s]", request.method, request.url.path, response.status_code, tag)
        return response

    # Bolt on data layer routes
    app.include_router(tables.router, prefix=f"{data_prefix}/tables")
    app.include_router(views.router, prefix=f"{data_prefix}/views")
    app.include_router(rpc_routes.router, prefix=f"{data_prefix}/rpc")
    app.include_router(ws.router, prefix=data_prefix)

    register_duckdb(app)

    # Provide config callback to dev.py via app state
    from n6k_server.build.run import _config_fn

    app.state._config_fn = _config_fn

    # Wrap lifespan to add data lifecycle
    original_lifespan = app.router.lifespan_context

    @asynccontextmanager
    async def lifespan(a):
        async with original_lifespan(a):
            a.state.shared_db = create_shared_db({}, {})

            async def _reaper_loop():
                while True:
                    await asyncio.sleep(300)
                    reap_expired_sessions(a)

            reaper_task = asyncio.create_task(_reaper_loop())
            yield
            reaper_task.cancel()
            try:
                a.state.shared_db.close()
            except Exception:
                pass
            for session in list(a.state.sessions.values()):
                destroy_session(session)
            a.state.sessions.clear()

    app.router.lifespan_context = lifespan

    return app


class DataSource:
    def __init__(self, app: FastAPI, name: str, fn):
        self._app = app
        self._fn = fn
        self._name = name

        app.state.table_registry.register_source(name, fn)

    def derive(self, name: str | None = None):
        def decorator(fn):
            resolved = name if name is not None else fn.__name__
            ds = DataSource.__new__(DataSource)
            ds._app = self._app
            ds._fn = fn
            ds._name = resolved
            self._app.state.table_registry.register_derivation(self._name, resolved, fn)
            return ds

        return decorator

    def view(self, name: str | None = None):
        def decorator(fn):
            resolved = name if name is not None else fn.__name__
            sql = fn()
            if not isinstance(sql, str):
                raise TypeError(f"@<data>.view('{resolved}') must return a SQL string, got {type(sql).__name__}")
            self._app.state.table_registry.register_view(resolved, sql, [self._name])
            return None

        return decorator

    def rpc(self, name: str | None = None):
        def decorator(fn):
            resolved = name if name is not None else fn.__name__
            self._app.state.table_registry.register_rpc(resolved, fn, parent_table=self._name)
            return fn

        return decorator


def data(app: FastAPI, name: str | None = None):
    def decorator(fn):
        resolved = name if name is not None else fn.__name__
        return DataSource(app, resolved, fn)

    return decorator


def rpc(app: FastAPI, name: str | None = None):
    def decorator(fn):
        resolved = name if name is not None else fn.__name__
        app.state.table_registry.register_rpc(resolved, fn, parent_table=None)
        return fn

    return decorator
