"""Tests for MCP Audit Trail."""

from __future__ import annotations

import json
import time
from pathlib import Path

import pytest

from mcpaudit.audit_trail import (
    AuditEntry,
    AuditLog,
    append_to_log,
    create_entry,
    create_fix_entry,
    create_guard_entry,
    create_scan_entry,
    export_csv,
    export_html,
    export_jsonl,
    format_audit_json,
    format_audit_text,
    load_log,
    save_log,
)


# --- AuditEntry ---

class TestAuditEntry:
    def test_creation(self):
        entry = AuditEntry(
            timestamp="2024-01-15T10:00:00Z",
            action="scan",
            target="/path/to/server.py",
            result="pass",
        )
        assert entry.action == "scan"
        assert entry.result == "pass"

    def test_auto_entry_id(self):
        entry = AuditEntry(
            timestamp="2024-01-15T10:00:00Z",
            action="scan",
            target="test.py",
            result="pass",
        )
        assert entry.entry_id.startswith("scan-")

    def test_custom_entry_id(self):
        entry = AuditEntry(
            timestamp="2024-01-15T10:00:00Z",
            action="scan",
            target="test.py",
            result="pass",
            entry_id="custom-123",
        )
        assert entry.entry_id == "custom-123"

    def test_to_dict(self):
        entry = AuditEntry(
            timestamp="2024-01-15T10:00:00Z",
            action="scan",
            target="test.py",
            result="pass",
            user="admin",
            details={"findings": 5},
        )
        d = entry.to_dict()
        assert d["action"] == "scan"
        assert d["user"] == "admin"
        assert d["details"]["findings"] == 5

    def test_to_dict_no_user(self):
        entry = AuditEntry(
            timestamp="2024-01-15T10:00:00Z",
            action="scan",
            target="test.py",
            result="pass",
        )
        d = entry.to_dict()
        assert "user" not in d

    def test_to_dict_no_details(self):
        entry = AuditEntry(
            timestamp="2024-01-15T10:00:00Z",
            action="scan",
            target="test.py",
            result="pass",
        )
        d = entry.to_dict()
        assert "details" not in d

    def test_from_dict(self):
        data = {
            "entry_id": "test-1",
            "timestamp": "2024-01-15T10:00:00Z",
            "action": "fix",
            "target": "config.json",
            "result": "pass",
            "user": "admin",
            "details": {"fixed": 3},
        }
        entry = AuditEntry.from_dict(data)
        assert entry.action == "fix"
        assert entry.user == "admin"

    def test_from_dict_minimal(self):
        data = {"action": "scan", "target": "test.py"}
        entry = AuditEntry.from_dict(data)
        assert entry.action == "scan"
        assert entry.result == ""


# --- AuditLog ---

class TestAuditLog:
    def _make_entries(self) -> list[AuditEntry]:
        return [
            AuditEntry("2024-01-15T10:00:00Z", "scan", "server.py", "pass", user="alice"),
            AuditEntry("2024-01-15T11:00:00Z", "scan", "app.py", "fail", user="bob"),
            AuditEntry("2024-01-15T12:00:00Z", "fix", "server.py", "pass", user="alice"),
            AuditEntry("2024-01-16T09:00:00Z", "guard", "config.json", "fail", user="alice"),
            AuditEntry("2024-01-16T10:00:00Z", "scan", "utils.py", "pass", user="bob"),
        ]

    def test_empty_log(self):
        log = AuditLog()
        assert log.total_entries == 0

    def test_append(self):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass"))
        assert log.total_entries == 1

    def test_total_entries(self):
        log = AuditLog(entries=self._make_entries())
        assert log.total_entries == 5

    def test_query_by_action(self):
        log = AuditLog(entries=self._make_entries())
        results = log.query(action="scan")
        assert len(results) == 3

    def test_query_by_target(self):
        log = AuditLog(entries=self._make_entries())
        results = log.query(target="server.py")
        assert len(results) == 2

    def test_query_by_result(self):
        log = AuditLog(entries=self._make_entries())
        results = log.query(result="fail")
        assert len(results) == 2

    def test_query_by_user(self):
        log = AuditLog(entries=self._make_entries())
        results = log.query(user="alice")
        assert len(results) == 3

    def test_query_by_since(self):
        log = AuditLog(entries=self._make_entries())
        results = log.query(since="2024-01-16T00:00:00Z")
        assert len(results) == 2

    def test_query_by_until(self):
        log = AuditLog(entries=self._make_entries())
        results = log.query(until="2024-01-15T11:00:00Z")
        assert len(results) == 2

    def test_query_with_limit(self):
        log = AuditLog(entries=self._make_entries())
        results = log.query(limit=2)
        assert len(results) == 2

    def test_query_combined(self):
        log = AuditLog(entries=self._make_entries())
        results = log.query(action="scan", user="alice")
        assert len(results) == 1

    def test_filter_by_date(self):
        log = AuditLog(entries=self._make_entries())
        results = log.filter_by_date("2024-01-15T00:00:00Z", "2024-01-15T23:59:59Z")
        assert len(results) == 3

    def test_filter_by_action(self):
        log = AuditLog(entries=self._make_entries())
        results = log.filter_by_action("fix")
        assert len(results) == 1

    def test_filter_by_target(self):
        log = AuditLog(entries=self._make_entries())
        results = log.filter_by_target("config")
        assert len(results) == 1

    def test_get_actions(self):
        log = AuditLog(entries=self._make_entries())
        actions = log.get_actions()
        assert "scan" in actions
        assert "fix" in actions
        assert "guard" in actions

    def test_get_targets(self):
        log = AuditLog(entries=self._make_entries())
        targets = log.get_targets()
        assert len(targets) == 4

    def test_get_users(self):
        log = AuditLog(entries=self._make_entries())
        users = log.get_users()
        assert "alice" in users
        assert "bob" in users

    def test_count_by_action(self):
        log = AuditLog(entries=self._make_entries())
        counts = log.count_by_action()
        assert counts["scan"] == 3
        assert counts["fix"] == 1
        assert counts["guard"] == 1

    def test_count_by_result(self):
        log = AuditLog(entries=self._make_entries())
        counts = log.count_by_result()
        assert counts["pass"] == 3
        assert counts["fail"] == 2

    def test_prune(self):
        entries = [
            AuditEntry("2020-01-01T00:00:00Z", "scan", "old.py", "pass"),
            AuditEntry(
                time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
                "scan", "new.py", "pass",
            ),
        ]
        log = AuditLog(entries=entries)
        removed = log.prune(days=30)
        assert removed == 1
        assert log.total_entries == 1

    def test_prune_none(self):
        entries = [
            AuditEntry(
                time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
                "scan", "new.py", "pass",
            ),
        ]
        log = AuditLog(entries=entries)
        removed = log.prune(days=30)
        assert removed == 0

    def test_to_dict(self):
        log = AuditLog(entries=self._make_entries()[:2])
        d = log.to_dict()
        assert d["total_entries"] == 2
        assert len(d["entries"]) == 2

    def test_from_dict(self):
        data = {
            "log_path": "/test/audit.log",
            "entries": [
                {
                    "entry_id": "test-1",
                    "timestamp": "2024-01-15T10:00:00Z",
                    "action": "scan",
                    "target": "test.py",
                    "result": "pass",
                },
            ],
        }
        log = AuditLog.from_dict(data)
        assert log.total_entries == 1
        assert log.log_path == "/test/audit.log"


# --- Entry Creation Helpers ---

class TestEntryHelpers:
    def test_create_entry(self):
        entry = create_entry("scan", "/test.py", "pass")
        assert entry.action == "scan"
        assert entry.timestamp != ""

    def test_create_entry_with_user(self):
        entry = create_entry("scan", "/test.py", "pass", user="admin")
        assert entry.user == "admin"

    def test_create_entry_with_details(self):
        entry = create_entry("scan", "/test.py", "pass", details={"count": 5})
        assert entry.details["count"] == 5

    def test_create_scan_entry_pass(self):
        entry = create_scan_entry("/test.py", findings_count=0, grade="A+")
        assert entry.action == "scan"
        assert entry.result == "pass"
        assert entry.details["grade"] == "A+"

    def test_create_scan_entry_fail(self):
        entry = create_scan_entry("/test.py", findings_count=5, grade="C")
        assert entry.result == "fail"
        assert entry.details["findings_count"] == 5

    def test_create_fix_entry_complete(self):
        entry = create_fix_entry("/test.py", fixed_count=3, total_count=3)
        assert entry.action == "fix"
        assert entry.result == "pass"

    def test_create_fix_entry_partial(self):
        entry = create_fix_entry("/test.py", fixed_count=2, total_count=5)
        assert entry.result == "partial"
        assert entry.details["fixed"] == 2

    def test_create_guard_entry_pass(self):
        entry = create_guard_entry("/config.json", issues_count=0)
        assert entry.action == "guard"
        assert entry.result == "pass"

    def test_create_guard_entry_fail(self):
        entry = create_guard_entry("/config.json", issues_count=3)
        assert entry.result == "fail"
        assert entry.details["issues_count"] == 3


# --- Persistence ---

class TestPersistence:
    def test_save_and_load(self, tmp_path):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass"))
        log.append(AuditEntry("2024-01-15T11:00:00Z", "fix", "test.py", "pass"))

        log_file = tmp_path / "audit.jsonl"
        save_log(log, log_file)

        loaded = load_log(log_file)
        assert loaded.total_entries == 2
        assert loaded.entries[0].action == "scan"
        assert loaded.entries[1].action == "fix"

    def test_load_nonexistent(self, tmp_path):
        log = load_log(tmp_path / "nonexistent.jsonl")
        assert log.total_entries == 0

    def test_append_to_log(self, tmp_path):
        log_file = tmp_path / "audit.jsonl"

        append_to_log(
            AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass"),
            log_file,
        )
        append_to_log(
            AuditEntry("2024-01-15T11:00:00Z", "fix", "test.py", "pass"),
            log_file,
        )

        loaded = load_log(log_file)
        assert loaded.total_entries == 2

    def test_save_creates_directory(self, tmp_path):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass"))
        log_file = tmp_path / "subdir" / "audit.jsonl"
        save_log(log, log_file)
        assert log_file.exists()


# --- Export Formats ---

class TestExportJsonl:
    def test_empty_log(self):
        log = AuditLog()
        output = export_jsonl(log)
        assert output == ""

    def test_with_entries(self):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass"))
        log.append(AuditEntry("2024-01-15T11:00:00Z", "fix", "app.py", "pass"))
        output = export_jsonl(log)
        lines = output.strip().split("\n")
        assert len(lines) == 2
        data = json.loads(lines[0])
        assert data["action"] == "scan"


class TestExportCsv:
    def test_empty_log(self):
        log = AuditLog()
        output = export_csv(log)
        assert "entry_id" in output  # Header row

    def test_with_entries(self):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass", user="admin"))
        output = export_csv(log)
        assert "scan" in output
        assert "admin" in output

    def test_csv_fields(self):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass"))
        output = export_csv(log)
        lines = output.strip().split("\n")
        assert len(lines) == 2  # header + 1 entry
        assert "timestamp" in lines[0]


class TestExportHtml:
    def test_empty_log(self):
        log = AuditLog()
        output = export_html(log)
        assert "<html" in output
        assert "MCP Audit Trail" in output

    def test_with_entries(self):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass"))
        log.append(AuditEntry("2024-01-15T11:00:00Z", "scan", "app.py", "fail"))
        output = export_html(log)
        assert "pass" in output
        assert "fail" in output
        assert "#2ecc71" in output  # green for pass
        assert "#e74c3c" in output  # red for fail

    def test_html_has_summary(self):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass"))
        output = export_html(log)
        assert "Total Entries" in output


# --- Text Formatters ---

class TestFormatAuditText:
    def test_empty_log(self):
        log = AuditLog()
        output = format_audit_text(log)
        assert "No audit entries" in output

    def test_with_entries(self):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass"))
        output = format_audit_text(log)
        assert "[OK]" in output
        assert "scan" in output

    def test_fail_marker(self):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "fail"))
        output = format_audit_text(log)
        assert "[FAIL]" in output

    def test_with_user(self):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass", user="admin"))
        output = format_audit_text(log)
        assert "admin" in output

    def test_with_details(self):
        log = AuditLog()
        log.append(AuditEntry(
            "2024-01-15T10:00:00Z", "scan", "test.py", "pass",
            details={"findings": 5},
        ))
        output = format_audit_text(log)
        assert "findings: 5" in output


class TestFormatAuditJson:
    def test_valid_json(self):
        log = AuditLog()
        log.append(AuditEntry("2024-01-15T10:00:00Z", "scan", "test.py", "pass"))
        output = format_audit_json(log)
        data = json.loads(output)
        assert data["total_entries"] == 1
