# -*- coding: utf-8 -*-
"""
Валидаторы ответа HTTP: условия (status_code_in, redirect_url_contains, ...),
композитные правила, ResponseValidator и билдеры (status_code, headers_key, headers_value, redirect).
"""
from dataclasses import dataclass
from typing import Callable, List, Dict, Type, Any
from urllib.parse import urlparse, parse_qs

from .exceptions import StatusCodeError, RedirectError, HeaderError
from .types import HttpResponse


ResponseCondition = Callable[[HttpResponse], bool]


def status_code_in(codes: List[int]) -> ResponseCondition:
    """Условие: статус-код ответа входит в список."""
    def _check(response: HttpResponse) -> bool:
        return response.status_code in codes
    return _check


def status_code_is(code: int) -> ResponseCondition:
    """Условие: статус-код равен заданному."""
    return status_code_in([code])


def redirect_url_contains(substring: str) -> ResponseCondition:
    """Условие: финальный URL или любой URL из истории редиректов содержит подстроку."""
    def _check(response: HttpResponse) -> bool:
        urls = [response.url] + [r.url for r in getattr(response, "history", [])]
        return any(substring in str(u) for u in urls)
    return _check


def header_equals(header_name: str, value: str) -> ResponseCondition:
    """Условие: заголовок присутствует и равен значению (без учёта регистра значения)."""
    key = header_name.lower()
    val_lower = value.lower()

    def _check(response: HttpResponse) -> bool:
        headers = getattr(response, "headers", {})
        if hasattr(headers, "get"):
            v = headers.get(header_name)
            if v is None:
                v = headers.get(key)
        else:
            v = headers.get(header_name) if isinstance(headers, dict) else None
        return v is not None and str(v).lower() == val_lower
    return _check


def header_contains(header_name: str, substring: str) -> ResponseCondition:
    """Условие: заголовок присутствует и содержит подстроку."""
    key = header_name.lower()
    sub_lower = substring.lower()

    def _check(response: HttpResponse) -> bool:
        headers = getattr(response, "headers", {})
        if hasattr(headers, "get"):
            v = headers.get(header_name)
            if v is None:
                v = headers.get(key)
        else:
            v = headers.get(header_name) if isinstance(headers, dict) else None
        return v is not None and sub_lower in str(v).lower()
    return _check


def header_missing(header_name: str) -> ResponseCondition:
    """Условие: заголовок отсутствует."""
    key = header_name.lower()

    def _check(response: HttpResponse) -> bool:
        headers = getattr(response, "headers", {})
        if hasattr(headers, "get"):
            return headers.get(header_name) is None and headers.get(key) is None
        if isinstance(headers, dict):
            return header_name not in headers and key not in {k.lower(): k for k in headers}
        return True
    return _check


@dataclass
class CompositeRule:
    """Одно групповое правило: все условия (AND) + исключение при совпадении."""
    conditions: List[ResponseCondition]
    raise_as: Type[Exception]
    message: str | None = None


# ---------------------------------------------------------------------------
# RedirectValidator
# ---------------------------------------------------------------------------

class RedirectValidator:
    """Валидатор редиректов."""

    def __init__(self) -> None:
        self._whitelist: List[str | Dict[str, Any]] | None = None
        self._blacklist: List[str | Dict[str, Any]] | None = None
        self._none_expected: bool = False
        self._raise_as: Type[Exception] | None = None
        self._raise_message: str | None = None

    def none_expected(self) -> "RedirectValidator":
        """Ожидается отсутствие редиректов."""
        self._none_expected = True
        return self

    def whitelist(self, urls: List[str | Dict[str, Any]]) -> "RedirectValidator":
        self._whitelist = urls
        return self

    def blacklist(self, urls: List[str | Dict[str, Any]]) -> "RedirectValidator":
        self._blacklist = urls
        return self

    def _raise_error(
        self, message: str, rule_type: str, details: Dict[str, Any] | None = None
    ) -> None:
        if self._raise_as is not None:
            if self._raise_message is not None:
                raise self._raise_as(self._raise_message)
            raise self._raise_as()
        raise RedirectError(message, rule_type=rule_type, details=details or {})

    def _match_url(self, actual_url: str, pattern: str | Dict[str, Any]) -> bool:
        if isinstance(pattern, str):
            return pattern in actual_url
        parsed = urlparse(actual_url)
        actual_params = parse_qs(parsed.query)
        for k, v in pattern.items():
            if k not in actual_params or str(v) not in actual_params[k]:
                return False
        return True

    def validate(self, response: HttpResponse) -> None:
        history_urls = [r.url for r in response.history]
        resp_url = str(response.url)

        if self._none_expected and history_urls:
            self._raise_error(
                f"Редиректы не ожидались, но произошли: {history_urls}",
                rule_type="redirect_none_expected",
                details={"history_urls": history_urls},
            )

        if self._whitelist:
            matched = any(
                any(self._match_url(h_url, p) for p in self._whitelist)
                for h_url in history_urls + [resp_url]
            )
            if not matched:
                self._raise_error(
                    f"URL {resp_url} или его история не в белом списке редиректов",
                    rule_type="redirect_whitelist",
                    details={"url": resp_url, "history_urls": history_urls},
                )

        if self._blacklist:
            for h_url in history_urls + [resp_url]:
                for pattern in self._blacklist:
                    if self._match_url(h_url, pattern):
                        self._raise_error(
                            f"Обнаружен запрещенный редирект/URL: {h_url}",
                            rule_type="redirect_blacklist",
                            details={"url": h_url},
                        )


class StatusCodeValidator:
    """Валидатор для статус-кодов."""

    def __init__(self) -> None:
        self._whitelist: List[int] | None = None
        self._blacklist: List[int] | None = None
        self._raise_as: Type[Exception] | None = None
        self._raise_message: str | None = None

    def whitelist(self, codes: List[int]) -> "StatusCodeValidator":
        self._whitelist = codes
        return self

    def blacklist(self, codes: List[int]) -> "StatusCodeValidator":
        self._blacklist = codes
        return self

    def _raise_error(
        self, message: str, rule_type: str, details: Dict[str, Any] | None = None
    ) -> None:
        if self._raise_as is not None:
            if self._raise_message is not None:
                raise self._raise_as(self._raise_message)
            raise self._raise_as()
        raise StatusCodeError(message, rule_type=rule_type, details=details or {})

    def validate(self, status_code: int) -> None:
        if self._whitelist is not None and status_code not in self._whitelist:
            self._raise_error(
                f"Статус-код {status_code} не разрешен. Разрешены: {self._whitelist}",
                rule_type="status_code_whitelist",
                details={"status_code": status_code, "whitelist": self._whitelist},
            )
        if self._blacklist is not None and status_code in self._blacklist:
            self._raise_error(
                f"Статус-код {status_code} запрещен. Запрещены: {self._blacklist}",
                rule_type="status_code_blacklist",
                details={"status_code": status_code, "blacklist": self._blacklist},
            )


class HeadersKeyValidator:
    """Валидатор для ключей заголовков."""

    def __init__(self) -> None:
        self._whitelist: List[str] | None = None
        self._blacklist: List[str] | None = None
        self._raise_as: Type[Exception] | None = None
        self._raise_message: str | None = None

    def whitelist(self, keys: List[str]) -> "HeadersKeyValidator":
        self._whitelist = [k.lower() for k in keys]
        return self

    def blacklist(self, keys: List[str]) -> "HeadersKeyValidator":
        self._blacklist = [k.lower() for k in keys]
        return self

    def _raise_error(
        self, message: str, rule_type: str, details: Dict[str, Any] | None = None
    ) -> None:
        if self._raise_as is not None:
            if self._raise_message is not None:
                raise self._raise_as(self._raise_message)
            raise self._raise_as()
        raise HeaderError(message, rule_type=rule_type, details=details or {})

    def validate(self, headers: Dict[str, str]) -> None:
        header_keys_lower = {k.lower(): k for k in headers.keys()}
        if self._whitelist is not None:
            invalid = [k for k in header_keys_lower if k not in self._whitelist]
            if invalid:
                actual_keys = [header_keys_lower[k] for k in invalid]
                self._raise_error(
                    f"Запрещенные ключи заголовков: {actual_keys}. Разрешены: {self._whitelist}",
                    rule_type="headers_key_whitelist",
                    details={"forbidden_keys": actual_keys, "whitelist": self._whitelist},
                )
        if self._blacklist is not None:
            found = [k for k in header_keys_lower if k in self._blacklist]
            if found:
                actual_keys = [header_keys_lower[k] for k in found]
                self._raise_error(
                    f"Запрещенные ключи заголовков: {actual_keys}",
                    rule_type="headers_key_blacklist",
                    details={"forbidden_keys": actual_keys},
                )


class HeadersValueValidator:
    """Валидатор для значений заголовков."""

    def __init__(self) -> None:
        self._header_name: str | None = None
        self._whitelist: List[str] | None = None
        self._blacklist: List[str] | None = None
        self._raise_as: Type[Exception] | None = None
        self._raise_message: str | None = None

    def for_header(self, header_name: str) -> "HeadersValueValidator":
        self._header_name = header_name.lower()
        return self

    def whitelist(self, values: List[str]) -> "HeadersValueValidator":
        self._whitelist = [v.lower() for v in values]
        return self

    def blacklist(self, values: List[str]) -> "HeadersValueValidator":
        self._blacklist = [v.lower() for v in values]
        return self

    def _raise_error(
        self, message: str, rule_type: str, details: Dict[str, Any] | None = None
    ) -> None:
        if self._raise_as is not None:
            if self._raise_message is not None:
                raise self._raise_as(self._raise_message)
            raise self._raise_as()
        raise HeaderError(message, rule_type=rule_type, details=details or {})

    def validate(self, headers: Dict[str, str]) -> None:
        if not self._header_name:
            return
        header_keys_lower = {k.lower(): k for k in headers.keys()}
        if self._header_name not in header_keys_lower:
            return
        actual_key = header_keys_lower[self._header_name]
        value = str(headers[actual_key]).lower()
        if self._whitelist is not None and value not in self._whitelist:
            self._raise_error(
                f"Значение '{headers[actual_key]}' для заголовка '{actual_key}' не разрешено. Разрешены: {self._whitelist}",
                rule_type="headers_value_whitelist",
                details={"header": actual_key, "value": headers[actual_key], "whitelist": self._whitelist},
            )
        if self._blacklist is not None and value in self._blacklist:
            self._raise_error(
                f"Значение '{headers[actual_key]}' для заголовка '{actual_key}' запрещено",
                rule_type="headers_value_blacklist",
                details={"header": actual_key, "value": headers[actual_key]},
            )


# ---------------------------------------------------------------------------
# ResponseValidator и билдеры
# ---------------------------------------------------------------------------

class ResponseValidator:
    """Главный валидатор для Response."""

    def __init__(self) -> None:
        self._status_code_validator: StatusCodeValidator | None = None
        self._headers_key_validator: HeadersKeyValidator | None = None
        self._headers_value_validators: List[HeadersValueValidator] = []
        self._redirect_validator: RedirectValidator | None = None
        self._composite_rules: List[CompositeRule] = []

    def when_all(
        self,
        *conditions: ResponseCondition,
        raise_as: Type[Exception],
        message: str | None = None,
    ) -> "ResponseValidator":
        if not conditions:
            raise TypeError("when_all требует хотя бы одно условие")
        self._composite_rules.append(
            CompositeRule(conditions=list(conditions), raise_as=raise_as, message=message)
        )
        return self

    def status_code(self) -> "StatusCodeValidatorBuilder":
        return StatusCodeValidatorBuilder(self)

    def headers_key(self) -> "HeadersKeyValidatorBuilder":
        return HeadersKeyValidatorBuilder(self)

    def headers_value(self) -> "HeadersValueValidatorBuilder":
        return HeadersValueValidatorBuilder(self)

    def redirect(self) -> "RedirectValidatorBuilder":
        return RedirectValidatorBuilder(self)

    def _validate_response(self, response: HttpResponse) -> None:
        for rule in self._composite_rules:
            if all(cond(response) for cond in rule.conditions):
                msg = rule.message or "Сработало групповое правило валидации"
                raise rule.raise_as(msg)
        if self._status_code_validator:
            self._status_code_validator.validate(response.status_code)
        if self._headers_key_validator:
            self._headers_key_validator.validate(dict(response.headers))
        for validator in self._headers_value_validators:
            validator.validate(dict(response.headers))
        if self._redirect_validator:
            self._redirect_validator.validate(response)


class StatusCodeValidatorBuilder:
    def __init__(self, parent: ResponseValidator) -> None:
        self._parent = parent
        self._validator = StatusCodeValidator()

    def whitelist(
        self,
        codes: List[int],
        raise_as: Type[Exception] | None = None,
        raise_message: str | None = None,
    ) -> ResponseValidator:
        self._validator.whitelist(codes)
        self._validator._raise_as = raise_as
        self._validator._raise_message = raise_message
        self._parent._status_code_validator = self._validator
        return self._parent

    def blacklist(
        self,
        codes: List[int],
        raise_as: Type[Exception] | None = None,
        raise_message: str | None = None,
    ) -> ResponseValidator:
        self._validator.blacklist(codes)
        self._validator._raise_as = raise_as
        self._validator._raise_message = raise_message
        self._parent._status_code_validator = self._validator
        return self._parent


class HeadersKeyValidatorBuilder:
    def __init__(self, parent: ResponseValidator) -> None:
        self._parent = parent
        self._validator = HeadersKeyValidator()

    def whitelist(
        self,
        keys: List[str],
        raise_as: Type[Exception] | None = None,
        raise_message: str | None = None,
    ) -> ResponseValidator:
        self._validator.whitelist(keys)
        self._validator._raise_as = raise_as
        self._validator._raise_message = raise_message
        self._parent._headers_key_validator = self._validator
        return self._parent

    def blacklist(
        self,
        keys: List[str],
        raise_as: Type[Exception] | None = None,
        raise_message: str | None = None,
    ) -> ResponseValidator:
        self._validator.blacklist(keys)
        self._validator._raise_as = raise_as
        self._validator._raise_message = raise_message
        self._parent._headers_key_validator = self._validator
        return self._parent


class HeadersValueValidatorBuilder:
    def __init__(self, parent: ResponseValidator) -> None:
        self._parent = parent
        self._validator = HeadersValueValidator()

    def for_header(self, header_name: str) -> "HeadersValueValidatorBuilder":
        self._validator.for_header(header_name)
        return self

    def whitelist(
        self,
        values: List[str],
        raise_as: Type[Exception] | None = None,
        raise_message: str | None = None,
    ) -> ResponseValidator:
        self._validator.whitelist(values)
        self._validator._raise_as = raise_as
        self._validator._raise_message = raise_message
        self._parent._headers_value_validators.append(self._validator)
        return self._parent

    def blacklist(
        self,
        values: List[str],
        raise_as: Type[Exception] | None = None,
        raise_message: str | None = None,
    ) -> ResponseValidator:
        self._validator.blacklist(values)
        self._validator._raise_as = raise_as
        self._validator._raise_message = raise_message
        self._parent._headers_value_validators.append(self._validator)
        return self._parent


class RedirectValidatorBuilder:
    def __init__(self, parent: ResponseValidator) -> None:
        self._parent = parent
        self._validator = RedirectValidator()

    def none_expected(
        self,
        raise_as: Type[Exception] | None = None,
        raise_message: str | None = None,
    ) -> ResponseValidator:
        self._validator.none_expected()
        self._validator._raise_as = raise_as
        self._validator._raise_message = raise_message
        self._parent._redirect_validator = self._validator
        return self._parent

    def whitelist(
        self,
        urls: List[str | Dict[str, Any]],
        raise_as: Type[Exception] | None = None,
        raise_message: str | None = None,
    ) -> ResponseValidator:
        self._validator.whitelist(urls)
        self._validator._raise_as = raise_as
        self._validator._raise_message = raise_message
        self._parent._redirect_validator = self._validator
        return self._parent

    def blacklist(
        self,
        urls: List[str | Dict[str, Any]],
        raise_as: Type[Exception] | None = None,
        raise_message: str | None = None,
    ) -> ResponseValidator:
        self._validator.blacklist(urls)
        self._validator._raise_as = raise_as
        self._validator._raise_message = raise_message
        self._parent._redirect_validator = self._validator
        return self._parent
