from __future__ import annotations

import importlib
import inspect
import typing
from abc import ABCMeta
from base64 import b64encode
from typing import Any
from urllib.parse import parse_qs

from ...util import parse_url
from .protocols import BaseResolver, ProtocolResolver


class ResolverFactory(metaclass=ABCMeta):
    @staticmethod
    def new(
        protocol: ProtocolResolver,
        specifier: str | None = None,
        implementation: str | None = None,
        **kwargs: Any,
    ) -> BaseResolver:
        package_name: str = __name__.split(".")[0]

        module_expr = f".{protocol.value.replace('-', '_')}"

        if implementation:
            module_expr += f"._{implementation.replace('-', '_').lower()}"

        spe_msg = " " if specifier is None else f' (w/ specifier "{specifier}") '

        try:
            resolver_module = importlib.import_module(
                module_expr, f"{package_name}.contrib.resolver"
            )
        except ImportError as e:
            raise NotImplementedError(
                f"{protocol}{spe_msg}cannot be loaded. Tried to import '{module_expr}'. Did you specify a non-existent implementation?"
            ) from e

        implementations: list[tuple[str, type[BaseResolver]]] = inspect.getmembers(
            resolver_module,
            lambda e: (
                isinstance(e, type)
                and issubclass(e, BaseResolver)
                and (
                    (specifier is None and e.specifier is None)
                    or specifier == e.specifier
                )
            ),
        )

        if not implementations:
            raise NotImplementedError(
                f"{protocol}{spe_msg}cannot be loaded. "
                "No compatible implementation available. "
                "Make sure your implementation inherit from BaseResolver."
            )

        implementation_target: type[BaseResolver] = implementations.pop()[1]

        return implementation_target(**kwargs)


class ResolverDescription:
    """Describe how a BaseResolver must be instantiated."""

    def __init__(
        self,
        protocol: ProtocolResolver,
        specifier: str | None = None,
        implementation: str | None = None,
        server: str | None = None,
        port: int | None = None,
        *host_patterns: str,
        **kwargs: typing.Any,
    ) -> None:
        self.protocol = protocol
        self.specifier = specifier
        self.implementation = implementation
        self.server = server
        self.port = port
        self.host_patterns = host_patterns
        self.kwargs = kwargs

    def __setitem__(self, key: str, value: typing.Any) -> None:
        self.kwargs[key] = value

    def __contains__(self, item: str) -> bool:
        return item in self.kwargs

    def new(self) -> BaseResolver:
        kwargs = {**self.kwargs}

        if self.server:
            kwargs["server"] = self.server
        if self.port:
            kwargs["port"] = self.port
        if self.host_patterns:
            kwargs["patterns"] = self.host_patterns

        return ResolverFactory.new(
            self.protocol,
            self.specifier,
            self.implementation,
            **kwargs,
        )

    @staticmethod
    def from_url(url: str) -> ResolverDescription:
        parsed_url = parse_url(url)

        schema = parsed_url.scheme

        if schema is None:
            raise ValueError("Given DNS url is missing a protocol")

        specifier = None
        implementation = None

        if "+" in schema:
            schema, specifier = tuple(schema.lower().split("+", 1))

        protocol = ProtocolResolver(schema)
        kwargs: dict[str, typing.Any] = {}

        if parsed_url.path:
            kwargs["path"] = parsed_url.path

        if parsed_url.auth:
            kwargs["headers"] = dict()
            if ":" in parsed_url.auth:
                username, password = parsed_url.auth.split(":")

                username = username.strip("'\"")
                password = password.strip("'\"")

                kwargs["headers"]["Authorization"] = (
                    f"Basic {b64encode(f'{username}:{password}'.encode()).decode()}"
                )
            else:
                kwargs["headers"]["Authorization"] = f"Bearer {parsed_url.auth}"

        if parsed_url.query:
            parameters = parse_qs(parsed_url.query)

            for parameter in parameters:
                if not parameters[parameter]:
                    continue

                parameter_insensible = parameter.lower()

                if (
                    isinstance(parameters[parameter], list)
                    and len(parameters[parameter]) > 1
                ):
                    if parameter == "implementation":
                        raise ValueError("Only one implementation can be passed to URL")

                    values = []

                    for e in parameters[parameter]:
                        if "," in e:
                            values.extend(e.split(","))
                        else:
                            values.append(e)

                    if parameter_insensible in kwargs:
                        if isinstance(kwargs[parameter_insensible], list):
                            kwargs[parameter_insensible].extend(values)
                        else:
                            values.append(kwargs[parameter_insensible])
                            kwargs[parameter_insensible] = values
                        continue

                    kwargs[parameter_insensible] = values
                    continue

                value: str = parameters[parameter][0].lower().strip(" ")

                if parameter == "implementation":
                    implementation = value
                    continue

                if "," in value:
                    list_of_values = value.split(",")

                    if parameter_insensible in kwargs:
                        if isinstance(kwargs[parameter_insensible], list):
                            kwargs[parameter_insensible].extend(list_of_values)
                        else:
                            list_of_values.append(kwargs[parameter_insensible])
                        continue

                    kwargs[parameter_insensible] = list_of_values
                    continue

                value_converted: bool | int | float | None = None

                if value in ["false", "true"]:
                    value_converted = True if value == "true" else False
                elif value.isdigit():
                    value_converted = int(value)
                elif (
                    value.count(".") == 1
                    and value.index(".") > 0
                    and value.replace(".", "").isdigit()
                ):
                    value_converted = float(value)

                kwargs[parameter_insensible] = (
                    value if value_converted is None else value_converted
                )

        host_patterns: list[str] = []

        if "hosts" in kwargs:
            host_patterns = (
                kwargs["hosts"].split(",")
                if isinstance(kwargs["hosts"], str)
                else kwargs["hosts"]
            )
            del kwargs["hosts"]

        return ResolverDescription(
            protocol,
            specifier,
            implementation,
            parsed_url.host,
            parsed_url.port,
            *host_patterns,
            **kwargs,
        )
