import inspect
import logging
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from prometheus_client import Counter, generate_latest, CONTENT_TYPE_LATEST
from typing import Callable, Dict, Any, Optional

from .parser import parse_ussd_request
from .response import ussd_response
from .sessions import get_or_create_session, get_session_by_id, get_step
from .context import _set_context, _clear_context, ussd

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)

# Prometheus metrics
METRIC_REQUESTS = Counter('cardisk_requests_total', 'Total USSD requests', ['type'])

class ScreenRoute:
    def __init__(self, name: str, handler: Callable[..., str]):
        self.name = name
        self.handler = handler
        self.options: Dict[str, Callable[..., str]] = {}
        self.fallback: Optional[Callable[..., str]] = None

    def option(self, value: str):
        def decorator(func: Callable[..., str]):
            self.options[value] = func
            return func
        return decorator

    def fallback_option(self):
        def decorator(func: Callable[..., str]):
            self.fallback = func
            return func
        return decorator

class Cardisk:
    def __init__(self):
        self.app = FastAPI()
        self.routes: Dict[str, ScreenRoute] = {}

        # metrics endpoint
        @self.app.get('/metrics')
        def metrics():
            return generate_latest()

        # error handler
        @self.app.exception_handler(Exception)
        async def generic_exception_handler(request: Request, exc: Exception):
            logger.exception('Unhandled error: %s', exc)
            return JSONResponse({'detail': 'internal server error'}, status_code=500)

        @self.app.post("/")
        async def handle_ussd(request: Request):
            body = await request.body()
            data = parse_ussd_request(body.decode())

            msisdn = data.get("msisdn", "")
            msg = data.get("msg", "") or ""
            req_type = int(data.get("type", 1))

            METRIC_REQUESTS.labels(type=str(req_type)).inc()
            logger.info("USSD request from %s type=%s msg=%s", msisdn, req_type, msg)

            # create or find session
            session_id = get_or_create_session(msisdn, req_type)
            session = get_session_by_id(session_id)

            # set context for this request
            _set_context(msisdn, session_id, msg)

            # determine current screen
            current = get_step(session_id)
            if req_type == 1:
                current = "start"

            route = self.routes.get(current)
            if not route:
                _clear_context()
                return ussd_response("Invalid route.", end_session=True)

            # if input matches an option, call that handler
            try:
                if msg and msg in route.options:
                    result = route.options[msg]()
                elif msg and "*" in route.options:
                    # treat '*' as wildcard registration too
                    result = route.options["*"](msg)
                elif msg and route.fallback:
                    result = route.fallback()
                else:
                    # call main handler
                    result = route.handler()
            except Exception as e:
                logger.exception('Handler error')
                _clear_context()
                return ussd_response(f"Handler error: {str(e)}", end_session=True)

            # clear context after handling
            end_now = get_step(session_id) == "end"
            _clear_context()
            return ussd_response(result, end_session=end_now)

    def route(self, name: str):
        def decorator(func: Callable[..., str]):
            route = ScreenRoute(name, func)
            self.routes[name] = route
            return route
        return decorator

# single default app for simple usage
app = Cardisk()
