"""Tests for pulse checker module."""

import json
import os
import tempfile
import time

import pytest

from pulse.checker import (
    CheckResult,
    Checker,
    VitalResult,
    format_duration,
    parse_duration,
)


class TestParseDuration:
    def test_seconds(self):
        assert parse_duration("30s") == 30

    def test_minutes(self):
        assert parse_duration("5m") == 300

    def test_hours(self):
        assert parse_duration("2h") == 7200

    def test_days(self):
        assert parse_duration("1d") == 86400

    def test_weeks(self):
        assert parse_duration("1w") == 604800

    def test_combined(self):
        assert parse_duration("1d12h") == 86400 + 43200

    def test_combined_hours_minutes(self):
        assert parse_duration("2h30m") == 7200 + 1800

    def test_empty_raises(self):
        with pytest.raises(ValueError):
            parse_duration("")

    def test_invalid_raises(self):
        with pytest.raises(ValueError):
            parse_duration("abc")

    def test_whitespace(self):
        assert parse_duration("  2h  ") == 7200


class TestFormatDuration:
    def test_seconds(self):
        assert format_duration(30) == "30s ago"

    def test_minutes(self):
        assert format_duration(120) == "2m ago"

    def test_hours(self):
        assert format_duration(7200) == "2h ago"

    def test_hours_minutes(self):
        assert format_duration(5400) == "1h30m ago"

    def test_days(self):
        assert format_duration(172800) == "2d ago"

    def test_none(self):
        assert format_duration(None) == "unknown"


class TestCheckResult:
    def test_to_dict(self):
        r = CheckResult("file_freshness", True, "ok", 60.0)
        d = r.to_dict()
        assert d["check_type"] == "file_freshness"
        assert d["passed"] is True
        assert d["last_activity_seconds"] == 60.0


class TestVitalResult:
    def test_all_passed(self):
        v = VitalResult("test", [
            CheckResult("a", True, "ok"),
            CheckResult("b", True, "ok"),
        ])
        assert v.passed is True
        assert v.severity == "OK"

    def test_some_failed(self):
        v = VitalResult("test", [
            CheckResult("a", True, "ok"),
            CheckResult("b", False, "fail"),
        ])
        assert v.passed is False
        assert v.severity == "WARN"
        assert v.failed_count == 1

    def test_all_failed(self):
        v = VitalResult("test", [
            CheckResult("a", False, "fail"),
            CheckResult("b", False, "fail"),
        ])
        assert v.severity == "CRIT"

    def test_last_activity(self):
        v = VitalResult("test", [
            CheckResult("a", True, "ok", 100.0),
            CheckResult("b", True, "ok", 50.0),
        ])
        assert v.last_activity_seconds == 50.0


class TestFileFreshness:
    def test_fresh_file(self, tmp_path):
        f = tmp_path / "data.json"
        f.write_text("{}")
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_file_freshness({"path": "data.json", "max_age": "1h"})
        assert result.passed is True

    def test_stale_file(self, tmp_path):
        f = tmp_path / "data.json"
        f.write_text("{}")
        # Set mtime to 3 hours ago
        old_time = time.time() - 10800
        os.utime(str(f), (old_time, old_time))
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_file_freshness({"path": "data.json", "max_age": "1h"})
        assert result.passed is False

    def test_missing_file(self, tmp_path):
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_file_freshness({"path": "nope.json", "max_age": "1h"})
        assert result.passed is False
        assert "not found" in result.message


class TestJsonKeyChanged:
    def test_first_observation(self, tmp_path):
        f = tmp_path / "state.json"
        f.write_text(json.dumps({"count": 42}))
        state_path = str(tmp_path / ".pulse_state.json")
        checker = Checker(base_dir=str(tmp_path), state_path=state_path)
        result = checker.check_json_key_changed({
            "path": "state.json", "key": "count", "max_age": "1h"
        })
        assert result.passed is True
        assert "first observation" in result.message

    def test_value_changed(self, tmp_path):
        f = tmp_path / "state.json"
        state_path = str(tmp_path / ".pulse_state.json")

        # First check
        f.write_text(json.dumps({"count": 42}))
        checker = Checker(base_dir=str(tmp_path), state_path=state_path)
        checker.check_json_key_changed({
            "path": "state.json", "key": "count", "max_age": "1h"
        })
        checker.state.save()

        # Change value
        f.write_text(json.dumps({"count": 43}))
        checker2 = Checker(base_dir=str(tmp_path), state_path=state_path)
        result = checker2.check_json_key_changed({
            "path": "state.json", "key": "count", "max_age": "1h"
        })
        assert result.passed is True
        assert "changed" in result.message

    def test_value_stale(self, tmp_path):
        f = tmp_path / "state.json"
        state_path = str(tmp_path / ".pulse_state.json")

        # Write state as if value hasn't changed for 2 hours
        f.write_text(json.dumps({"count": 42}))
        state_data = {
            "json_key:state.json:count": {
                "value": 42,
                "changed_at": time.time() - 7200,
            }
        }
        with open(state_path, "w") as sf:
            json.dump(state_data, sf)

        checker = Checker(base_dir=str(tmp_path), state_path=state_path)
        result = checker.check_json_key_changed({
            "path": "state.json", "key": "count", "max_age": "1h"
        })
        assert result.passed is False
        assert "has not changed" in result.message

    def test_dotted_key(self, tmp_path):
        f = tmp_path / "state.json"
        f.write_text(json.dumps({"stats": {"count": 5}}))
        state_path = str(tmp_path / ".pulse_state.json")
        checker = Checker(base_dir=str(tmp_path), state_path=state_path)
        result = checker.check_json_key_changed({
            "path": "state.json", "key": "stats.count", "max_age": "1h"
        })
        assert result.passed is True

    def test_missing_key(self, tmp_path):
        f = tmp_path / "state.json"
        f.write_text(json.dumps({"other": 1}))
        state_path = str(tmp_path / ".pulse_state.json")
        checker = Checker(base_dir=str(tmp_path), state_path=state_path)
        result = checker.check_json_key_changed({
            "path": "state.json", "key": "count", "max_age": "1h"
        })
        assert result.passed is False
        assert "not found" in result.message


class TestLogEntry:
    def test_pattern_found_recent(self, tmp_path):
        log = tmp_path / "app.log"
        ts = time.strftime("%Y-%m-%d %H:%M:%S")
        log.write_text(f"[{ts}] cycle completed successfully\n")
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_log_entry({
            "path": "app.log", "pattern": "cycle completed", "max_age": "1h"
        })
        assert result.passed is True

    def test_pattern_not_found(self, tmp_path):
        log = tmp_path / "app.log"
        log.write_text("just some random logs\n")
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_log_entry({
            "path": "app.log", "pattern": "cycle completed", "max_age": "1h"
        })
        assert result.passed is False
        assert "not found" in result.message

    def test_missing_log(self, tmp_path):
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_log_entry({
            "path": "nope.log", "pattern": "test", "max_age": "1h"
        })
        assert result.passed is False
        assert "not found" in result.message

    def test_stale_pattern(self, tmp_path):
        log = tmp_path / "app.log"
        old_ts = "2020-01-01 00:00:00"
        log.write_text(f"[{old_ts}] cycle completed\n")
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_log_entry({
            "path": "app.log", "pattern": "cycle completed", "max_age": "1h"
        })
        assert result.passed is False


class TestProcessOutput:
    def test_successful_command(self, tmp_path):
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_process_output({
            "command": "echo hello",
            "exit_code": 0,
        })
        assert result.passed is True

    def test_failed_exit_code(self, tmp_path):
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_process_output({
            "command": "exit 1",
            "exit_code": 0,
        })
        assert result.passed is False

    def test_output_pattern_match(self, tmp_path):
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_process_output({
            "command": "echo 'version 2.0'",
            "output_pattern": r"version \d+\.\d+",
        })
        assert result.passed is True

    def test_output_pattern_no_match(self, tmp_path):
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_process_output({
            "command": "echo hello",
            "output_pattern": "goodbye",
        })
        assert result.passed is False

    def test_timeout(self, tmp_path):
        checker = Checker(base_dir=str(tmp_path))
        result = checker.check_process_output({
            "command": "sleep 10",
            "timeout": 1,
        })
        assert result.passed is False
        assert "timed out" in result.message


class TestRunAll:
    def test_run_all_vitals(self, tmp_path):
        f = tmp_path / "data.json"
        f.write_text("{}")
        state_path = str(tmp_path / ".pulse_state.json")
        checker = Checker(base_dir=str(tmp_path), state_path=state_path)
        config = {
            "vitals": [
                {
                    "name": "Test Service",
                    "checks": [
                        {"type": "file_freshness", "path": "data.json", "max_age": "1h"}
                    ],
                }
            ]
        }
        results = checker.run_all(config)
        assert len(results) == 1
        assert results[0].passed is True

    def test_name_filter(self, tmp_path):
        f = tmp_path / "data.json"
        f.write_text("{}")
        state_path = str(tmp_path / ".pulse_state.json")
        checker = Checker(base_dir=str(tmp_path), state_path=state_path)
        config = {
            "vitals": [
                {"name": "A", "checks": [{"type": "file_freshness", "path": "data.json", "max_age": "1h"}]},
                {"name": "B", "checks": [{"type": "file_freshness", "path": "data.json", "max_age": "1h"}]},
            ]
        }
        results = checker.run_all(config, name_filter="A")
        assert len(results) == 1
        assert results[0].name == "A"

    def test_unknown_check_type(self, tmp_path):
        state_path = str(tmp_path / ".pulse_state.json")
        checker = Checker(base_dir=str(tmp_path), state_path=state_path)
        config = {
            "vitals": [
                {"name": "X", "checks": [{"type": "nonexistent"}]},
            ]
        }
        results = checker.run_all(config)
        assert results[0].checks[0].passed is False
        assert "Unknown" in results[0].checks[0].message
