import json
import inspect
import importlib
from parse import parse
from starlette.requests import Request
from starlette.responses import Response, JSONResponse

class App:
    def __init__(self, settings_module=None):
        self.routes = {}
        self.middleware_classes = []
        if settings_module:
            self.settings = importlib.import_module(settings_module)
            self.register_apps()

    # 1. The Entry Point: Must be an async __call__
    async def __call__(self, scope, receive, send):
        if scope['type'] != 'http':
            return

        request = Request(scope, receive)
        response = await self.handle_request(request)
        await response(scope, receive, send)

    def register_apps(self):
        apps = getattr(self.settings, "INSTALLED_APPS", [])
        for app_path in apps:
            module = importlib.import_module(app_path)
            app_routes = getattr(module, "routes", [])
            for path, handler in app_routes:
                self.add_route(path, handler)

    def add_route(self, path, handler):
        assert path not in self.routes, f"Route {path} already exists."
        self.routes[path] = handler

    # 2. Async Handler Execution
    async def handle_request(self, request):
        handler, kwargs = self.find_handler(request_path=request.url.path)

        if handler is None:
            return Response("Not found.", status_code=404)

        if inspect.isclass(handler):
            handler_instance = handler()
            handler = getattr(handler_instance, request.method.lower(), None)
            if handler is None:
                return Response("Method not allowed.", status_code=405)

        # 3. Support both Async and Sync handlers
        if inspect.iscoroutinefunction(handler):
            result = await handler(request, **kwargs)
        else:
            result = handler(request, **kwargs)

        # 4. Automatic JSON handling
        if isinstance(result, dict):
            return JSONResponse(result)
        
        return result

    def find_handler(self, request_path):
        for path, handler in self.routes.items():
            parse_result = parse(path, request_path)
            if parse_result is not None:
                return handler, parse_result.named
        return None, None