#!/usr/bin/env python3
# Copyright: 2026, Maximiliano Curia <maxy@debian.org>
#
# License: ISC
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#  .
#  THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
#  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#  AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
#  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
#  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
#  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
#  PERFORMANCE OF THIS SOFTWARE.

"""Tests for scripts/dcs_client.py, the shared Debian Code Search client."""

from __future__ import annotations

import hashlib
import sys
import urllib.request
from pathlib import Path
from typing import Any

import pytest

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "scripts"))

import dcs_client  # noqa: E402

from tests.dcs_helpers import FakeTransport  # noqa: E402

CANNED = b'[{"package": "p", "path": "p/COPYING"}]'


def _client(cache_dir: Path, **kw: Any) -> Any:
    kw.setdefault("user_agent", "test-agent")
    config = dcs_client.DcsConfig(key="key", cache_dir=cache_dir, sleep=0.0, **kw)
    return dcs_client.DcsClient(config)


def _serve(monkeypatch: pytest.MonkeyPatch, transport: FakeTransport) -> None:
    monkeypatch.setattr(urllib.request, "urlopen", transport)


def test_cache_roundtrip(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    transport = FakeTransport([CANNED])
    _serve(monkeypatch, transport)
    client = _client(tmp_path)
    first = client.search_live("q", "literal")
    second = client.search_live("q", "literal")  # served from cache
    assert first.state is dcs_client.SearchState.HITS
    assert first.hits == [{"package": "p", "path": "p/COPYING"}]
    assert second == first
    assert transport.calls == 1


def test_outcome_hits(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    transport = FakeTransport([CANNED])
    _serve(monkeypatch, transport)
    outcome = _client(tmp_path).search_live("q", "literal")
    assert outcome.state is dcs_client.SearchState.HITS
    assert outcome.hits == [{"package": "p", "path": "p/COPYING"}]


def test_outcome_broad(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    # A response larger than the cap is broad-by-design; shrink the cap so a
    # tiny payload trips it rather than fabricating 25 MB.
    monkeypatch.setattr(dcs_client, "MAX_RESPONSE_BYTES", 4)
    _serve(monkeypatch, FakeTransport([CANNED]))
    outcome = _client(tmp_path).search_live("q", "literal")
    assert outcome.state is dcs_client.SearchState.BROAD
    assert outcome.hits == []


def test_search_cached_pending_when_uncached(tmp_path: Path) -> None:
    outcome = _client(tmp_path).search_cached("q", "literal")
    assert outcome.state is dcs_client.SearchState.PENDING


def test_cache_key_prefix_disjoint(tmp_path: Path) -> None:
    prefixed = _client(tmp_path, cache_prefix="rank")
    plain = _client(tmp_path, cache_prefix="")
    assert prefixed._cache_path("q", "literal") != plain._cache_path("q", "literal")


def test_rank_cache_key_matches_legacy_scheme(tmp_path: Path) -> None:
    # The existing on-disk cache was written with this exact key; the prefixed
    # client must keep resolving to the same filename or the cache is orphaned.
    client = _client(tmp_path, cache_prefix="rank")
    legacy = hashlib.sha256(b"rank\x00literal\x00q").hexdigest()[:20] + ".json"
    assert client._cache_path("q", "literal").name == legacy


def test_plain_cache_key_matches_legacy_scheme(tmp_path: Path) -> None:
    client = _client(tmp_path, cache_prefix="")
    legacy = hashlib.sha256(b"literal\x00q").hexdigest()[:20] + ".json"
    assert client._cache_path("q", "literal").name == legacy


def test_corrupt_cache_is_treated_as_miss(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    transport = FakeTransport([CANNED])
    _serve(monkeypatch, transport)
    client = _client(tmp_path)
    client._cache_path("q", "literal").write_text("{ not json", encoding="utf-8")
    outcome = client.search_live("q", "literal")
    assert outcome.state is dcs_client.SearchState.HITS  # refetched, no crash
    assert outcome.hits == [{"package": "p", "path": "p/COPYING"}]
    assert transport.calls == 1
