"""
SAYTHON Framework - Main Application Class

Usage:
    from saython import Saython

    app = Saython(__name__)

    @app.get("/")
    def index(req):
        return {"message": "Hello, World!"}

    if __name__ == "__main__":
        app.run()
"""
import json
import traceback
from typing import Any

from saython.router import Router
from saython.request import Request
from saython.response import Response
from saython.exceptions import SaythonError


class Saython:
    """
    The core SAYTHON application.

    Quick start:
        app = Saython(__name__)

        @app.get("/items")
        def list_items(req):
            return Item.all_as_dict()       # list → JSON

        @app.post("/items")
        def create_item(req):
            return Item.create(**req.json)  # dict → JSON 200

        app.run(port=8000)
    """

    def __init__(self, name: str = "saython"):
        self.name = name
        self._router = Router()
        self.config: dict = {
            "DEBUG": False,
            "SECRET_KEY": "saython-secret-change-me",
            "DATABASE_URL": "saython.db",
            "TOKEN_EXPIRY_HOURS": 24,
            "HOST": "127.0.0.1",
            "PORT": 8000,
            "CORS": False,
            "CORS_ORIGINS": ["*"],
        }
        self._global_middlewares: list = []
        self._before_request_hooks: list = []
        self._after_request_hooks: list = []
        self._error_handlers: dict = {}

    # ------------------------------------------------------------------ #
    #  Configuration
    # ------------------------------------------------------------------ #
    def configure(self, **kwargs):
        """
        Set config values.

            app.configure(
                SECRET_KEY="super-secret",
                DATABASE_URL="myapp.db",
                DEBUG=True,
                CORS=True,
            )
        """
        self.config.update(kwargs)
        # Propagate to sub-modules
        from saython import orm, auth
        if "DATABASE_URL" in kwargs:
            orm.configure_db(kwargs["DATABASE_URL"])
        if "SECRET_KEY" in kwargs:
            auth.set_secret_key(kwargs["SECRET_KEY"])
        if "TOKEN_EXPIRY_HOURS" in kwargs:
            auth.set_token_expiry(kwargs["TOKEN_EXPIRY_HOURS"])
        return self

    # ------------------------------------------------------------------ #
    #  Route registration (delegated to Router)
    # ------------------------------------------------------------------ #
    def get(self, path: str, middlewares: list = None):
        return self._router.get(path, middlewares)

    def post(self, path: str, middlewares: list = None):
        return self._router.post(path, middlewares)

    def put(self, path: str, middlewares: list = None):
        return self._router.put(path, middlewares)

    def patch(self, path: str, middlewares: list = None):
        return self._router.patch(path, middlewares)

    def delete(self, path: str, middlewares: list = None):
        return self._router.delete(path, middlewares)

    def route(self, path: str, methods: list = None, middlewares: list = None):
        return self._router.route(path, methods, middlewares)

    def include(self, prefix: str, router: Router):
        """Mount a sub-router under a URL prefix."""
        self._router.include(prefix, router)
        return self

    # ------------------------------------------------------------------ #
    #  Hooks & middleware
    # ------------------------------------------------------------------ #
    def before_request(self, fn):
        """Register a function that runs before every request."""
        self._before_request_hooks.append(fn)
        return fn

    def after_request(self, fn):
        """Register a function that runs after every request (receives Response)."""
        self._after_request_hooks.append(fn)
        return fn

    def use(self, middleware):
        """Add a global WSGI middleware wrapper."""
        self._global_middlewares.append(middleware)
        return self

    def error_handler(self, status_code: int):
        """Register a custom error handler for a status code."""
        def decorator(fn):
            self._error_handlers[status_code] = fn
            return fn
        return decorator

    # ------------------------------------------------------------------ #
    #  Request → Response pipeline
    # ------------------------------------------------------------------ #
    def _dispatch(self, req: Request) -> Response:
        """Core dispatch: resolve route → run handler → build Response."""

        # Pre-request hooks
        for hook in self._before_request_hooks:
            result = hook(req)
            if result is not None:
                return self._make_response(result)

        # Normalize path: strip trailing slash (except for root "/")
        path = req.path.rstrip("/") or "/"
        handler, params, route_middlewares = self._router.resolve(req.method, path)
        req.params = params

        # Apply route-level middlewares
        fn = handler
        for mw in reversed(route_middlewares or []):
            fn = mw(fn)

        raw = fn(req)
        return self._make_response(raw)

    @staticmethod
    def _make_response(raw: Any) -> Response:
        """Convert any handler return value into a Response."""
        if isinstance(raw, Response):
            return raw
        if isinstance(raw, (dict, list)):
            return Response(raw, 200)
        if isinstance(raw, tuple):
            body, status = raw[0], raw[1] if len(raw) > 1 else 200
            return Response(body, status)
        if raw is None:
            return Response.no_content()
        return Response(raw, 200)

    # ------------------------------------------------------------------ #
    #  WSGI callable
    # ------------------------------------------------------------------ #
    def __call__(self, environ, start_response):
        req = Request(environ)
        try:
            resp = self._dispatch(req)
        except SaythonError as exc:
            custom = self._error_handlers.get(exc.status_code)
            if custom:
                resp = self._make_response(custom(req, exc))
            else:
                resp = Response(exc.to_dict(), exc.status_code)
        except Exception as exc:
            if self.config.get("DEBUG"):
                body = {"error": str(exc), "traceback": traceback.format_exc()}
            else:
                body = {"error": "Internal server error"}
            resp = Response(body, 500)

        # After-request hooks
        for hook in self._after_request_hooks:
            try:
                hook(req, resp)
            except Exception:
                pass

        # CORS headers
        if self.config.get("CORS"):
            origins = self.config.get("CORS_ORIGINS", ["*"])
            resp.headers["Access-Control-Allow-Origin"] = ", ".join(origins)
            resp.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS"
            resp.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"

        # Handle OPTIONS preflight
        if req.method == "OPTIONS":
            resp = Response(None, 204)
            if self.config.get("CORS"):
                resp.headers["Access-Control-Allow-Origin"] = "*"
                resp.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS"
                resp.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"

        start_response(resp.status_line, resp.wsgi_headers)
        return [resp.body]

    # ------------------------------------------------------------------ #
    #  Development server
    # ------------------------------------------------------------------ #
    def run(self, host: str = None, port: int = None, debug: bool = None, reload: bool = False):
        """
        Start the built-in development server.

            app.run()                        # 127.0.0.1:8000
            app.run(host="0.0.0.0", port=5000, debug=True)
        """
        from wsgiref.simple_server import make_server, WSGIRequestHandler
        import logging

        _host = host or self.config.get("HOST", "127.0.0.1")
        _port = port or self.config.get("PORT", 8000)
        _debug = debug if debug is not None else self.config.get("DEBUG", False)

        if _debug:
            self.config["DEBUG"] = True

        # Suppress noisy default WSGI logging unless debug
        if not _debug:
            class _QuietHandler(WSGIRequestHandler):
                def log_message(self, fmt, *args):
                    pass
            handler_class = _QuietHandler
        else:
            handler_class = WSGIRequestHandler

        print(f"\n  SAYTHON  dev server")
        print(f"  Running on  http://{_host}:{_port}")
        print(f"  Debug mode: {'ON' if _debug else 'OFF'}")
        print(f"  Press Ctrl+C to stop\n")

        with make_server(_host, _port, self, handler_class=handler_class) as httpd:
            try:
                httpd.serve_forever()
            except KeyboardInterrupt:
                print("\n  Server stopped.")

    # ------------------------------------------------------------------ #
    #  Introspection helpers
    # ------------------------------------------------------------------ #
    def routes(self) -> list:
        """Return a list of all registered routes (method, path)."""
        return [(r.method, r.path) for r in self._router._routes]

    def print_routes(self):
        """Pretty-print all registered routes."""
        print(f"\n  {'METHOD':<8}  PATH")
        print(f"  {'------':<8}  ----")
        for method, path in self.routes():
            print(f"  {method:<8}  {path}")
        print()
