import re
from nexios.middlewares.base import BaseMiddleware
from nexios.http import Request,Response
from nexios.config import get_config
from typing import Callable, Optional,List,Dict,Any
import typing

ALL_METHODS = ("delete", "get", "head", "options", "patch", "post", "put")
BASIC_HEADERS = {"Accept", "Accept-Language", "Content-Language", "Content-Type"}
SAFELISTED_HEADERS = {"accept", "accept-language", "content-language", "content-type"}

class CORSMiddleware(BaseMiddleware):
    def __init__(self):
        config = get_config().cors

        if not config:
            return None
        self.allow_origins :List[str] = config.allow_origins or []
        self.blacklist_origins :List[str] = config.blacklist_origins or []
        self.allow_methods = config.allow_methods or ALL_METHODS
        self.blacklist_headers :List[str] = config.blacklist_headers or []
        self.allow_credentials = config.allow_credentials if config.allow_credentials is not None else True
        self.allow_origin_regex = re.compile(config.allow_origin_regex) if config.allow_origin_regex else None
        self.expose_headers :List[str] = config.expose_headers or []
        self.max_age = config.max_age or 600
        self.strict_origin_checking = config.strict_origin_checking or False
        self.dynamic_origin_validator: Optional[Callable[[Optional[str]], bool]] = getattr(config, "dynamic_origin_validator", None)
        self.debug = config.debug or False
        self.custom_error_status = config.custom_error_status or 400
        self.custom_error_messages = getattr(config, "custom_error_messages", {}) or {}

        self.simple_headers :Dict[str,Any]= {}
        if self.allow_credentials:
            self.simple_headers["Access-Control-Allow-Credentials"] = "true"
        if self.expose_headers:
            self.simple_headers["Access-Control-Expose-Headers"] = ", ".join(self.expose_headers)

        self.preflight_headers = {
            "Access-Control-Allow-Methods": ", ".join([x.upper() for x in self.allow_methods]),
            "Access-Control-Max-Age": str(self.max_age),
        }
        if self.allow_credentials:
            self.preflight_headers["Access-Control-Allow-Credentials"] = "true"
        if config.allow_headers:
            self.allow_headers :List[str] = [*list(SAFELISTED_HEADERS),*config.allow_headers]
        else:
            self.allow_headers  = list(SAFELISTED_HEADERS)
    async def process_request(self, request: Request,response :  Response, call_next :  typing.Callable[..., typing.Awaitable[Any]]):
        config = get_config().cors
        if not config:
            await call_next()
            return None

        origin = request.origin
        method = request.scope["method"]

        if not origin and self.strict_origin_checking:
            if self.debug:
                print("Request denied: Missing 'Origin' header.")
            return response.json(self.get_error_message("missing_origin"), status_code=self.custom_error_status)

        if method.lower() == "options" and "access-control-request-method" in request.headers:
            return await self.preflight_response(request, response)
        await call_next()

    async def process_response(self, request: Request, response: Response):
        config = get_config().cors

        if not config:
            return None
        origin = request.origin

        if origin and self.is_allowed_origin(origin):
            response.headers["Access-Control-Allow-Origin"] = origin

            if self.allow_credentials:
                response.headers["Access-Control-Allow-Credentials"] = "true"

        if self.expose_headers:
            response.headers["Access-Control-Expose-Headers"] = ", ".join(self.expose_headers)

    def is_allowed_origin(self, origin: str | None) -> bool:
        if origin in self.blacklist_origins:
            if self.debug:
                print(f"Request denied: Origin '{origin}' is blacklisted.")

            return False

        if "*" in self.allow_origins:

            return True

        if self.allow_origin_regex and self.allow_origin_regex.fullmatch(origin):
            return True

        if self.dynamic_origin_validator and callable(self.dynamic_origin_validator):
            return self.dynamic_origin_validator(origin)

        return origin in self.allow_origins
    def is_allowed_method(self,method :Optional[str]) -> bool:

        if "*" in self.allow_methods:
            return True
        if not (method or "").lower() in [x.lower() for x in self.allow_methods]:

            return False
        return True
    async def preflight_response(self, request: Request, response: Response) -> Response:
        origin = request.headers.get("origin")
        requested_method = request.headers.get("access-control-request-method")
        requested_headers = request.headers.get("access-control-request-headers")

        headers = self.preflight_headers.copy()


        if not self.is_allowed_origin(origin):


            if self.debug:
                print(f"Preflight request denied: Origin '{origin}' is not allowed.")
            return response.json(self.get_error_message("disallowed_origin"), status_code=self.custom_error_status)

        headers["Access-Control-Allow-Origin"] = origin #type:ignore


        if not self.is_allowed_method(requested_method):
            if self.debug:
                print(f"Preflight request denied: Method '{requested_method}' is not allowed.")

                return response.json(self.get_error_message("disallowed_method"), status_code=self.custom_error_status)

        # Handle headers
        if requested_headers:
            requested_header_list = [h.strip().lower() for h in requested_headers.split(",")]
            if "*" in self.allow_headers:
                headers["Access-Control-Allow-Headers"] = "*"
            else:
                for header in requested_header_list:
                    if header not in self.allow_headers or header in self.blacklist_headers:
                        if self.debug:
                            print(f"Preflight request denied: Header '{header}' is not allowed.")
                        return response.json(self.get_error_message("disallowed_header"), status_code=self.custom_error_status)
                headers["Access-Control-Allow-Headers"] = requested_headers
        return response.json("OK", status_code=201, headers=headers)

    def get_error_message(self, error_type: str) -> str:
        return self.custom_error_messages.get(error_type, "CORS request denied.")
