"""Tests for scanner/__main__.py — CLI commands, worst_level, file-lock."""
import json
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import MagicMock, patch

import pytest

from scanner.__main__ import _worst_level, _LEVEL_ORDER


# ---------------------------------------------------------------------------
# _worst_level
# ---------------------------------------------------------------------------

class TestWorstLevel:
    def test_all_green(self):
        assert _worst_level(["green", "green"]) == "green"

    def test_green_yellow(self):
        assert _worst_level(["green", "yellow"]) == "yellow"

    def test_green_red(self):
        assert _worst_level(["green", "red"]) == "red"

    def test_yellow_red(self):
        assert _worst_level(["yellow", "red"]) == "red"

    def test_red_yellow(self):
        assert _worst_level(["red", "yellow"]) == "red"

    def test_single_green(self):
        assert _worst_level(["green"]) == "green"

    def test_empty_returns_green(self):
        assert _worst_level([]) == "green"

    def test_many_levels(self):
        assert _worst_level(["green", "green", "yellow", "green", "red"]) == "red"


class TestLevelOrder:
    def test_order_values(self):
        assert _LEVEL_ORDER["green"] == 0
        assert _LEVEL_ORDER["yellow"] == 1
        assert _LEVEL_ORDER["red"] == 2

    def test_order_increasing(self):
        assert _LEVEL_ORDER["green"] < _LEVEL_ORDER["yellow"] < _LEVEL_ORDER["red"]


# ---------------------------------------------------------------------------
# cmd_scan file-lock
# ---------------------------------------------------------------------------

class TestCmdScanFileLock:
    @patch("scanner.__main__._crawl_and_extract")
    @patch("scanner.__main__.BaselineManager")
    def test_scan_without_data_dir_no_crash(self, mock_bm, mock_crawl):
        """cmd_scan ohne data_dir sollte keinen Lock versuchen."""
        from scanner.__main__ import cmd_scan

        mock_crawl.return_value = ([], [])
        mock_bm_instance = MagicMock()
        mock_bm_instance.baseline_exists.return_value = False
        mock_bm.return_value = mock_bm_instance

        cfg = {"target": "https://test.de", "data_dir": None}
        args = MagicMock(config="test/config.yaml")
        result = cmd_scan(args, cfg)
        assert result == 0

    @patch("scanner.__main__._crawl_and_extract")
    @patch("scanner.__main__.BaselineManager")
    def test_scan_with_data_dir_creates_lock(self, mock_bm, mock_crawl):
        """cmd_scan mit data_dir erstellt .scan.lock."""
        from scanner.__main__ import cmd_scan
        import fcntl

        with TemporaryDirectory() as tmp:
            lock_path = Path(tmp) / ".scan.lock"
            mock_crawl.return_value = ([], [])
            mock_bm_instance = MagicMock()
            mock_bm_instance.baseline_exists.return_value = False
            mock_bm.return_value = mock_bm_instance

            cfg = {"target": "https://test.de", "data_dir": tmp}
            args = MagicMock(config="test/config.yaml")
            result = cmd_scan(args, cfg)

            assert result == 0
            # Lock wurde erstellt und nach Scan wieder entfernt (oder leer)
            assert lock_path.exists()


# ---------------------------------------------------------------------------
# cmd_scan baseline_exists
# ---------------------------------------------------------------------------

class TestCmdScanNoBaseline:
    @patch("scanner.__main__._crawl_and_extract")
    @patch("scanner.__main__.BaselineManager")
    def test_no_baseline_returns_zero(self, mock_bm, mock_crawl):
        from scanner.__main__ import cmd_scan

        with TemporaryDirectory() as tmp:
            mock_crawl.return_value = ([{"url": "https://test.de/"}], [])
            mock_bm_instance = MagicMock()
            mock_bm_instance.baseline_exists.return_value = False
            mock_bm.return_value = mock_bm_instance

            cfg = {"target": "https://test.de", "data_dir": tmp}
            args = MagicMock(config="test/config.yaml")
            result = cmd_scan(args, cfg)
            assert result == 0


# ---------------------------------------------------------------------------
# _crawl_and_extract
# ---------------------------------------------------------------------------

class TestCrawlAndExtract:
    @patch("scanner.__main__.Crawler")
    def test_empty_crawl(self, mock_crawler_cls):
        from scanner.__main__ import _crawl_and_extract

        mock_crawler = MagicMock()
        mock_crawler.crawl.return_value = {
            "target": "https://test.de",
            "pages": [],
            "errors": [{"url": "https://test.de/", "error": "timeout"}],
        }
        mock_crawler_cls.return_value = mock_crawler

        cfg = {
            "target": "https://test.de",
            "crawl": {"max_pages": 10, "delay_seconds": 0.1, "skip_extensions": [],
                       "exclude_paths": [], "include_paths": [], "sitemap": False},
            "request_timeout": 10,
            "user_agent": "test",
        }
        pages, errors = _crawl_and_extract(cfg)
        assert len(pages) == 0
        assert len(errors) == 1

    @patch("scanner.__main__.Crawler")
    def test_crawl_with_pages(self, mock_crawler_cls):
        from scanner.__main__ import _crawl_and_extract

        mock_crawler = MagicMock()
        mock_crawler.crawl.return_value = {
            "target": "https://test.de",
            "pages": [
                {"url": "https://test.de/", "status": 200,
                 "html": "<html><body>Test</body></html>", "headers": {}},
            ],
            "errors": [],
        }
        mock_crawler_cls.return_value = mock_crawler

        cfg = {
            "target": "https://test.de",
            "crawl": {"max_pages": 10, "delay_seconds": 0.1, "skip_extensions": [],
                       "exclude_paths": [], "include_paths": [], "sitemap": False},
            "request_timeout": 10,
            "user_agent": "test",
        }
        pages, errors = _crawl_and_extract(cfg)
        assert len(pages) == 1
        assert len(errors) == 0
        assert "text" in pages[0]  # extract_page wurde aufgerufen


# ---------------------------------------------------------------------------
# _run_content_check
# ---------------------------------------------------------------------------

class TestRunContentCheck:
    def test_empty_baseline_and_snapshot(self):
        from scanner.__main__ import _run_content_check

        cfg = {
            "target": "https://test.de",
            "config_dir": "/tmp/test/config",
            "security_headers": [],
        }
        snap = {"pages": {}, "errors": []}
        baseline = {"manifest": {}, "pages": {}}

        diff, assessment, wl_violations, missing_sec = _run_content_check(cfg, snap, baseline)
        assert assessment["level"] == "green"
        assert assessment["score"] == 0


# ---------------------------------------------------------------------------
# Score aggregation in cmd_scan
# ---------------------------------------------------------------------------

class TestScoreAggregation:
    @patch("scanner.__main__._crawl_and_extract")
    @patch("scanner.__main__.BaselineManager")
    @patch("scanner.__main__._run_content_check")
    @patch("scanner.__main__._build_report")
    @patch("scanner.__main__._write_report")
    @patch("scanner.__main__._print_summary")
    @patch("scanner.__main__.send_alert")
    def test_scan_aggregates_levels(self, mock_alert, mock_print, mock_write,
                                     mock_build, mock_check, mock_bm, mock_crawl):
        from scanner.__main__ import cmd_scan

        with TemporaryDirectory() as tmp:
            mock_crawl.return_value = ([{"url": "https://test.de/"}], [])

            mock_bm_instance = MagicMock()
            mock_bm_instance.baseline_exists.return_value = True
            mock_bm_instance.load_baseline.return_value = {"manifest": {}, "pages": {}}
            mock_bm.return_value = mock_bm_instance

            # content check: yellow
            mock_check.return_value = (
                {"new_external_domains": [], "page_diffs": []},
                {"level": "yellow", "score": 30, "reasons": ["Test"], "exit_code": 1},
                [], {}
            )

            cfg = {
                "target": "https://test.de",
                "data_dir": tmp,
                "reports_dir": f"{tmp}/reports",
                "ai_analysis": {},
                "periodic_checks": {"enabled": False},
            }
            args = MagicMock(config="test/config.yaml")
            result = cmd_scan(args, cfg)
            assert result == 1  # exit_code für yellow
