# -*- coding: utf-8 -*-
"""Тесты валидаторов: статус-код, заголовки (ключи/значения), редиректы, условия, when_all."""
import pytest

from request_pydantic_decorator import (
    ResponseValidator,
    status_code_in,
    status_code_is,
    redirect_url_contains,
    header_equals,
    header_contains,
    header_missing,
    Exceptions,
    AuthError,
    StatusCodeError,
    RedirectError,
    HeaderError,
)

from conftest import FakeResponse, make_response


# ---------- Условия (conditions) ----------


class TestConditions:
    def test_status_code_in_true(self):
        cond = status_code_in([200, 201])
        r = make_response(status_code=200)
        assert cond(r) is True
        r2 = make_response(status_code=201)
        assert cond(r2) is True

    def test_status_code_in_false(self):
        cond = status_code_in([200, 201])
        r = make_response(status_code=404)
        assert cond(r) is False

    def test_status_code_is(self):
        assert status_code_is(200)(make_response(status_code=200)) is True
        assert status_code_is(200)(make_response(status_code=201)) is False

    def test_redirect_url_contains_final_url(self):
        cond = redirect_url_contains("login")
        r = make_response(url="https://example.com/login")
        assert cond(r) is True

    def test_redirect_url_contains_in_history(self):
        cond = redirect_url_contains("auth")
        r = FakeResponse(url="https://example.com/ok", history=[
            type("R", (), {"url": "https://example.com/auth"})(),
        ])
        assert cond(r) is True

    def test_redirect_url_contains_missing(self):
        cond = redirect_url_contains("login")
        r = make_response(url="https://example.com/")
        assert cond(r) is False

    def test_header_equals_case_insensitive_value(self):
        cond = header_equals("Content-Type", "application/json")
        r = make_response(headers={"Content-Type": "APPLICATION/JSON"})
        assert cond(r) is True

    def test_header_equals_missing(self):
        cond = header_equals("X-Custom", "yes")
        r = make_response(headers={})
        assert cond(r) is False

    def test_header_contains(self):
        cond = header_contains("Content-Type", "json")
        r = make_response(headers={"Content-Type": "application/json"})
        assert cond(r) is True
        r2 = make_response(headers={"Content-Type": "text/html"})
        assert cond(r2) is False

    def test_header_missing_present(self):
        cond = header_missing("X-Secret")
        r = make_response(headers={"Content-Type": "application/json"})
        assert cond(r) is True

    def test_header_missing_absent(self):
        cond = header_missing("Content-Type")
        r = make_response(headers={"Content-Type": "application/json"})
        assert cond(r) is False


# ---------- Status code validator ----------


class TestStatusCodeValidator:
    def test_whitelist_pass(self):
        v = ResponseValidator().status_code().whitelist([200, 201])
        r = make_response(status_code=200)
        v._validate_response(r)

    def test_whitelist_fail_raises(self):
        v = ResponseValidator().status_code().whitelist([200])
        r = make_response(status_code=404)
        with pytest.raises(StatusCodeError) as exc_info:
            v._validate_response(r)
        assert "404" in str(exc_info.value)
        assert exc_info.value.rule_type == "status_code_whitelist"

    def test_blacklist_pass(self):
        v = ResponseValidator().status_code().blacklist([500, 502])
        r = make_response(status_code=200)
        v._validate_response(r)

    def test_blacklist_fail_raises(self):
        v = ResponseValidator().status_code().blacklist([500])
        r = make_response(status_code=500)
        with pytest.raises(StatusCodeError) as exc_info:
            v._validate_response(r)
        assert "500" in str(exc_info.value)

    def test_whitelist_raise_as_custom_exception(self):
        v = (
            ResponseValidator()
            .status_code()
            .whitelist([200], raise_as=Exceptions.Auth.ErrorCreds, raise_message="Кастомное сообщение")
        )
        r = make_response(status_code=403)
        with pytest.raises(Exceptions.Auth.ErrorCreds) as exc_info:
            v._validate_response(r)
        assert exc_info.value.message == "Кастомное сообщение"

    def test_whitelist_raise_as_default_message(self):
        v = (
            ResponseValidator()
            .status_code()
            .whitelist([200], raise_as=Exceptions.Auth.ErrorCaptcha)
        )
        r = make_response(status_code=500)
        with pytest.raises(Exceptions.Auth.ErrorCaptcha) as exc_info:
            v._validate_response(r)
        assert "капч" in exc_info.value.message.lower() or "капча" in exc_info.value.message.lower()


# ---------- Headers key validator ----------


class TestHeadersKeyValidator:
    def test_whitelist_pass(self):
        v = ResponseValidator().headers_key().whitelist(["Content-Type", "X-Request-Id"])
        r = make_response(headers={"Content-Type": "application/json", "X-Request-Id": "abc"})
        v._validate_response(r)

    def test_whitelist_extra_key_fail(self):
        v = ResponseValidator().headers_key().whitelist(["Content-Type"])
        r = make_response(headers={"Content-Type": "application/json", "X-Unknown": "1"})
        with pytest.raises(HeaderError) as exc_info:
            v._validate_response(r)
        assert "X-Unknown" in str(exc_info.value) or "forbidden" in str(exc_info.value).lower()

    def test_blacklist_fail(self):
        v = ResponseValidator().headers_key().blacklist(["X-Secret"])
        r = make_response(headers={"Content-Type": "application/json", "X-Secret": "no"})
        with pytest.raises(HeaderError):
            v._validate_response(r)

    def test_blacklist_pass(self):
        v = ResponseValidator().headers_key().blacklist(["X-Secret"])
        r = make_response(headers={"Content-Type": "application/json"})
        v._validate_response(r)

    def test_headers_case_insensitive_keys(self):
        v = ResponseValidator().headers_key().whitelist(["content-type"])
        r = make_response(headers={"Content-Type": "application/json"})
        v._validate_response(r)


# ---------- Headers value validator ----------


class TestHeadersValueValidator:
    def test_for_header_whitelist_pass(self):
        v = (
            ResponseValidator()
            .headers_value()
            .for_header("Content-Type")
            .whitelist(["application/json", "text/html"])
        )
        r = make_response(headers={"Content-Type": "application/json"})
        v._validate_response(r)

    def test_for_header_whitelist_fail(self):
        v = (
            ResponseValidator()
            .headers_value()
            .for_header("Content-Type")
            .whitelist(["application/json"])
        )
        r = make_response(headers={"Content-Type": "text/xml"})
        with pytest.raises(HeaderError):
            v._validate_response(r)

    def test_for_header_blacklist_fail(self):
        v = (
            ResponseValidator()
            .headers_value()
            .for_header("X-Mode")
            .blacklist(["debug"])
        )
        r = make_response(headers={"X-Mode": "debug"})
        with pytest.raises(HeaderError):
            v._validate_response(r)

    def test_for_header_missing_header_skipped(self):
        v = (
            ResponseValidator()
            .headers_value()
            .for_header("X-Custom")
            .whitelist(["yes"])
        )
        r = make_response(headers={"Content-Type": "application/json"})
        v._validate_response(r)

    def test_value_case_insensitive(self):
        v = (
            ResponseValidator()
            .headers_value()
            .for_header("Content-Type")
            .whitelist(["application/json"])
        )
        r = make_response(headers={"Content-Type": "APPLICATION/JSON"})
        v._validate_response(r)


# ---------- Redirect validator ----------


class TestRedirectValidator:
    def test_none_expected_pass(self):
        v = ResponseValidator().redirect().none_expected()
        r = make_response(url="https://example.com/", history=[])
        v._validate_response(r)

    def test_none_expected_fail(self):
        v = ResponseValidator().redirect().none_expected()
        r = FakeResponse(url="https://example.com/", history=[type("R", (), {"url": "https://example.com/redirect"})()])
        with pytest.raises(RedirectError) as exc_info:
            v._validate_response(r)
        assert "редирек" in str(exc_info.value).lower() or "redirect" in str(exc_info.value).lower()

    def test_whitelist_pass(self):
        v = ResponseValidator().redirect().whitelist(["example.com/ok"])
        r = make_response(url="https://example.com/ok")
        v._validate_response(r)

    def test_whitelist_fail(self):
        v = ResponseValidator().redirect().whitelist(["example.com/allowed"])
        r = make_response(url="https://example.com/other")
        with pytest.raises(RedirectError):
            v._validate_response(r)

    def test_blacklist_fail(self):
        v = ResponseValidator().redirect().blacklist(["evil.com"])
        r = FakeResponse(url="https://evil.com/", history=[])
        with pytest.raises(RedirectError):
            v._validate_response(r)

    def test_blacklist_pass(self):
        v = ResponseValidator().redirect().blacklist(["evil.com"])
        r = make_response(url="https://example.com/")
        v._validate_response(r)


# ---------- Composite rules (when_all) ----------


class TestWhenAll:
    def test_when_all_matched_raises(self):
        v = (
            ResponseValidator()
            .when_all(
                status_code_is(404),
                redirect_url_contains("error"),
                raise_as=AuthError,
                message="Не найдено",
            )
        )
        r = FakeResponse(
            status_code=404,
            url="https://example.com/error",
            history=[],
        )
        with pytest.raises(AuthError) as exc_info:
            v._validate_response(r)
        assert "Не найдено" in str(exc_info.value)

    def test_when_all_not_matched_no_raise(self):
        v = (
            ResponseValidator()
            .when_all(
                status_code_is(404),
                redirect_url_contains("error"),
                raise_as=AuthError,
            )
        )
        r = make_response(status_code=404, url="https://example.com/ok")
        v._validate_response(r)

    def test_when_all_empty_raises_type_error(self):
        v = ResponseValidator()
        with pytest.raises(TypeError, match="хотя бы одно условие"):
            v.when_all(raise_as=StatusCodeError)

    def test_when_all_then_status_code_order(self):
        v = (
            ResponseValidator()
            .when_all(status_code_is(200), raise_as=ValueError, message="skip")
            .status_code()
            .whitelist([200])
        )
        r = make_response(status_code=200)
        with pytest.raises(ValueError, match="skip"):
            v._validate_response(r)


# ---------- Комбинированные валидаторы ----------


class TestCombinedValidators:
    def test_status_and_headers_and_redirect(self):
        v = (
            ResponseValidator()
            .status_code().whitelist([200])
            .headers_key().whitelist(["content-type"])
            .redirect().none_expected()
        )
        r = make_response(
            status_code=200,
            headers={"Content-Type": "application/json"},
            url="https://example.com/",
            history=[],
        )
        v._validate_response(r)

    def test_status_fail_short_circuit(self):
        v = (
            ResponseValidator()
            .status_code().whitelist([200])
            .headers_key().blacklist(["X-Bad"])
        )
        r = make_response(status_code=500, headers={"X-Bad": "1"})
        with pytest.raises(StatusCodeError):
            v._validate_response(r)

    def test_multiple_headers_value_validators(self):
        v = (
            ResponseValidator()
            .headers_value().for_header("Content-Type").whitelist(["application/json"])
            .headers_value().for_header("X-Version").whitelist(["1", "2"])
        )
        r = make_response(headers={"Content-Type": "application/json", "X-Version": "1"})
        v._validate_response(r)
