"""Tests for asset hashing and comparison."""
from unittest.mock import MagicMock, patch

import pytest
import requests

from scanner.baseline import BaselineManager
from scanner.checker import fetch_asset_hashes
from scanner.differ import compare_asset_hashes, score_asset_diff

CFG = {"scoring": {}, "thresholds": {}}


# ---------------------------------------------------------------------------
# fetch_asset_hashes
# ---------------------------------------------------------------------------

class TestFetchAssetHashes:
    def _mock_session(self, status=200, content=b"data"):
        resp = MagicMock()
        resp.status_code = status
        resp.iter_content.return_value = [content]
        resp.close = MagicMock()
        inst = MagicMock()
        inst.get.return_value = resp
        inst.close = MagicMock()
        return inst

    def test_successful_fetch_returns_sha256(self):
        import hashlib
        data = b"hello asset"
        expected = hashlib.sha256(data).hexdigest()

        with patch("scanner.checker.requests.Session") as MockSession:
            MockSession.return_value = self._mock_session(content=data)
            results = fetch_asset_hashes(["https://x.de/app.js"], delay=0)

        assert results["https://x.de/app.js"]["sha256"] == expected
        assert results["https://x.de/app.js"]["error"] is None

    def test_http_error_recorded(self):
        with patch("scanner.checker.requests.Session") as MockSession:
            MockSession.return_value = self._mock_session(status=404)
            results = fetch_asset_hashes(["https://x.de/missing.js"], delay=0)

        assert results["https://x.de/missing.js"]["sha256"] is None
        assert "HTTP 404" in results["https://x.de/missing.js"]["error"]

    def test_connection_error_recorded(self):
        with patch("scanner.checker.requests.Session") as MockSession:
            inst = MockSession.return_value
            inst.get.side_effect = requests.exceptions.ConnectionError("refused")
            inst.close = MagicMock()
            results = fetch_asset_hashes(["https://unreachable.de/x.js"], delay=0)

        assert results["https://unreachable.de/x.js"]["sha256"] is None
        assert results["https://unreachable.de/x.js"]["error"] is not None

    def test_empty_list_returns_empty(self):
        with patch("scanner.checker.requests.Session"):
            results = fetch_asset_hashes([], delay=0)
        assert results == {}


# ---------------------------------------------------------------------------
# compare_asset_hashes
# ---------------------------------------------------------------------------

class TestCompareAssetHashes:
    def _h(self, sha, type_="img", size=100):
        return {"sha256": sha, "size": size, "type": type_, "error": None}

    def test_no_changes_empty_diff(self):
        h = {"https://x.de/img.jpg": self._h("aaa")}
        result = compare_asset_hashes(h, h)
        assert result["changed"] == []
        assert result["new"] == []
        assert result["missing"] == []

    def test_changed_hash_detected(self):
        baseline = {"https://x.de/img.jpg": self._h("aaa111")}
        current = {"https://x.de/img.jpg": self._h("bbb222")}
        result = compare_asset_hashes(baseline, current)
        assert len(result["changed"]) == 1
        assert result["changed"][0]["url"] == "https://x.de/img.jpg"

    def test_new_asset_detected(self):
        baseline = {}
        current = {"https://x.de/new.jpg": self._h("abc")}
        result = compare_asset_hashes(baseline, current)
        assert "https://x.de/new.jpg" in result["new"]

    def test_missing_asset_detected(self):
        baseline = {"https://x.de/old.jpg": self._h("abc")}
        current = {}
        result = compare_asset_hashes(baseline, current)
        assert "https://x.de/old.jpg" in result["missing"]

    def test_fetch_error_not_in_changed(self):
        baseline = {"https://x.de/img.jpg": self._h("aaa")}
        current = {"https://x.de/img.jpg": {"sha256": None, "size": None, "type": "img", "error": "timeout"}}
        result = compare_asset_hashes(baseline, current)
        assert result["changed"] == []
        assert len(result["fetch_errors"]) == 1


# ---------------------------------------------------------------------------
# score_asset_diff
# ---------------------------------------------------------------------------

class TestScoreAssetDiff:
    def test_no_changes_green(self):
        result = score_asset_diff({"changed": [], "new": [], "missing": [], "fetch_errors": [], "total_checked": 5}, CFG)
        assert result["level"] == "green"
        assert result["score"] == 0

    def test_changed_script_scores_40(self):
        diff = {"changed": [{"url": "https://x.de/app.js", "type": "script", "old_sha256": "a…", "new_sha256": "b…", "size_diff": 0}],
                "new": [], "missing": [], "fetch_errors": [], "total_checked": 1}
        result = score_asset_diff(diff, CFG)
        assert result["score"] == 40
        assert result["level"] == "yellow"

    def test_changed_stylesheet_scores_30(self):
        diff = {"changed": [{"url": "https://x.de/style.css", "type": "link_rel", "old_sha256": "a…", "new_sha256": "b…", "size_diff": 0}],
                "new": [], "missing": [], "fetch_errors": [], "total_checked": 1}
        result = score_asset_diff(diff, CFG)
        assert result["score"] == 30
        assert result["level"] == "yellow"

    def test_changed_image_scores_20(self):
        diff = {"changed": [{"url": "https://x.de/logo.png", "type": "img", "old_sha256": "a…", "new_sha256": "b…", "size_diff": 100}],
                "new": [], "missing": [], "fetch_errors": [], "total_checked": 1}
        result = score_asset_diff(diff, CFG)
        assert result["score"] == 20
        assert result["level"] == "yellow"

    def test_two_changed_scripts_red(self):
        entry = {"url": "https://x.de/x.js", "type": "script", "old_sha256": "a…", "new_sha256": "b…", "size_diff": 0}
        diff = {"changed": [entry, {**entry, "url": "https://x.de/y.js"}],
                "new": [], "missing": [], "fetch_errors": [], "total_checked": 2}
        result = score_asset_diff(diff, CFG)
        assert result["score"] == 80
        assert result["level"] == "red"
        assert result["exit_code"] == 2


class TestAutoAssetBaselineOnApprove:
    """approve --all soll die Datei-Baseline automatisch mit anlegen."""

    def test_establish_asset_baseline_saves_hashes(self, tmp_path):
        from scanner.__main__ import _establish_asset_baseline

        bm = BaselineManager(tmp_path)
        page = {
            "url": "https://x.de/",
            "status": 200,
            "links": {"script": [{"url": "https://x.de/app.js", "class": "internal"}],
                      "link_rel": [], "img": []},
        }
        snap_dir = bm.save_snapshot([page])

        with patch("scanner.__main__.fetch_asset_hashes") as mock_fetch:
            mock_fetch.return_value = {
                "https://x.de/app.js": {"sha256": "abc123", "size": 10, "error": None}
            }
            _establish_asset_baseline({"request_timeout": 15}, bm, snap_dir)

        saved = bm.load_asset_hashes()
        assert "https://x.de/app.js" in saved
        assert saved["https://x.de/app.js"]["sha256"] == "abc123"

    def test_no_assets_writes_nothing(self, tmp_path):
        from scanner.__main__ import _establish_asset_baseline

        bm = BaselineManager(tmp_path)
        page = {"url": "https://x.de/", "status": 200,
                "links": {"script": [], "link_rel": [], "img": []}}
        snap_dir = bm.save_snapshot([page])

        with patch("scanner.__main__.fetch_asset_hashes") as mock_fetch:
            _establish_asset_baseline({"request_timeout": 15}, bm, snap_dir)
            mock_fetch.assert_not_called()

        assert bm.load_asset_hashes() == {}
