from __future__ import annotations

from logging import WARNING
from typing import TYPE_CHECKING

import pytest

from scrapy.http import Request, Response
from scrapy.spidermiddlewares.metacopy import MetaCopyDetectionMiddleware
from scrapy.spiders import Spider
from scrapy.utils.misc import build_from_crawler
from scrapy.utils.test import get_crawler

if TYPE_CHECKING:
    from scrapy.crawler import Crawler


def make_response(url: str = "https://example.com") -> Response:
    response = Response(url)
    response.request = Request(url)
    return response


@pytest.fixture
def crawler() -> Crawler:
    return get_crawler(Spider)


@pytest.fixture
def mw(crawler: Crawler) -> MetaCopyDetectionMiddleware:
    return build_from_crawler(MetaCopyDetectionMiddleware, crawler)


def process(
    mw: MetaCopyDetectionMiddleware,
    requests: list[Request],
    response: Response | None = None,
) -> list[Request]:
    if response is None:
        response = make_response()
    return list(mw.process_spider_output(response, requests))


def test_no_warning_for_clean_request(
    mw: MetaCopyDetectionMiddleware, caplog: pytest.LogCaptureFixture
) -> None:
    req = Request("https://example.com/1", meta={"my_key": "value"})
    with caplog.at_level(WARNING):
        process(mw, [req])
    assert not caplog.records


def test_warns_on_internal_key(
    mw: MetaCopyDetectionMiddleware, caplog: pytest.LogCaptureFixture
) -> None:
    req = Request("https://example.com/1", meta={"retry_times": 1})
    with caplog.at_level(WARNING):
        process(mw, [req])
    assert len(caplog.records) == 1
    assert "retry_times" in caplog.text
    assert "https://example.com/1" in caplog.text


def test_warns_once_across_multiple_requests(
    mw: MetaCopyDetectionMiddleware, caplog: pytest.LogCaptureFixture
) -> None:
    reqs = [
        Request("https://example.com/1", meta={"retry_times": 1}),
        Request("https://example.com/2", meta={"retry_times": 2}),
    ]
    with caplog.at_level(WARNING):
        process(mw, reqs)
    assert len(caplog.records) == 1


def test_reports_all_found_keys(
    mw: MetaCopyDetectionMiddleware, caplog: pytest.LogCaptureFixture
) -> None:
    req = Request(
        "https://example.com/1",
        meta={"retry_times": 1, "redirect_times": 2},
    )
    with caplog.at_level(WARNING):
        process(mw, [req])
    assert "retry_times" in caplog.text
    assert "redirect_times" in caplog.text


def test_includes_source_response_in_message(
    mw: MetaCopyDetectionMiddleware, caplog: pytest.LogCaptureFixture
) -> None:
    response = make_response("https://source.example.com")
    req = Request("https://example.com/1", meta={"retry_times": 1})
    with caplog.at_level(WARNING):
        process(mw, [req], response=response)
    assert "https://source.example.com" in caplog.text


def test_skip_keys_setting(caplog: pytest.LogCaptureFixture) -> None:
    with caplog.at_level(WARNING):
        crawler = get_crawler(Spider, {"META_COPY_WARN_SKIP_KEYS": ["retry_times"]})
        mw = build_from_crawler(MetaCopyDetectionMiddleware, crawler)
        req = Request("https://example.com/1", meta={"retry_times": 1})
        process(mw, [req])
    assert not caplog.records


def test_skip_keys_setting_partial(caplog: pytest.LogCaptureFixture) -> None:
    with caplog.at_level(WARNING):
        crawler = get_crawler(Spider, {"META_COPY_WARN_SKIP_KEYS": ["retry_times"]})
        mw = build_from_crawler(MetaCopyDetectionMiddleware, crawler)
        req = Request(
            "https://example.com/1",
            meta={"retry_times": 1, "redirect_times": 2},
        )
        process(mw, [req])
    assert len(caplog.records) == 1
    assert "retry_times" not in caplog.text
    assert "redirect_times" in caplog.text


def test_no_warning_for_start_requests(
    mw: MetaCopyDetectionMiddleware, caplog: pytest.LogCaptureFixture
) -> None:
    req = Request("https://example.com/1", meta={"retry_times": 1})
    with caplog.at_level(WARNING):
        list(mw.process_spider_output(None, [req]))
    assert not caplog.records
