"""Tests for scheduler integration."""

from __future__ import annotations

import json
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock, patch

import pytest


# ── Interval parsing ───────────────────────────────────────────────────────


class TestParseInterval:
    """Test human-readable interval parsing."""

    def test_minutes(self):
        from alive.integrations.scheduler import parse_interval

        assert parse_interval("5m") == 300
        assert parse_interval("30m") == 1800

    def test_hours(self):
        from alive.integrations.scheduler import parse_interval

        assert parse_interval("1h") == 3600
        assert parse_interval("2h") == 7200

    def test_days(self):
        from alive.integrations.scheduler import parse_interval

        assert parse_interval("1d") == 86400

    def test_weeks(self):
        from alive.integrations.scheduler import parse_interval

        assert parse_interval("1w") == 604800

    def test_seconds(self):
        from alive.integrations.scheduler import parse_interval

        assert parse_interval("30s") == 30

    def test_invalid_raises(self):
        from alive.integrations.scheduler import parse_interval

        with pytest.raises(ValueError, match="Invalid interval"):
            parse_interval("abc")

    def test_invalid_unit_raises(self):
        from alive.integrations.scheduler import parse_interval

        with pytest.raises(ValueError, match="Invalid interval"):
            parse_interval("5x")

    def test_whitespace_handling(self):
        from alive.integrations.scheduler import parse_interval

        assert parse_interval("  30m  ") == 1800


class TestFormatInterval:
    """Test interval formatting."""

    def test_seconds(self):
        from alive.integrations.scheduler import format_interval

        assert format_interval(30) == "30s"

    def test_minutes(self):
        from alive.integrations.scheduler import format_interval

        assert format_interval(1800) == "30m"

    def test_hours(self):
        from alive.integrations.scheduler import format_interval

        assert format_interval(7200) == "2h"

    def test_days(self):
        from alive.integrations.scheduler import format_interval

        assert format_interval(86400) == "1d"

    def test_weeks(self):
        from alive.integrations.scheduler import format_interval

        assert format_interval(604800) == "1w"


# ── Schedule CRUD ──────────────────────────────────────────────────────────


class TestScheduleCRUD:
    """Test schedule create/load/save/remove."""

    def test_create_and_load(self, tmp_path):
        from alive.integrations.scheduler import (
            ScheduleTask,
            create_schedule,
            load_schedules,
            save_schedules,
        )

        schedule_file = tmp_path / "schedules.json"

        with patch("alive.integrations.scheduler._schedules_path", return_value=schedule_file):
            task = create_schedule(
                "check bitcoin price",
                interval="30m",
                notify_via="telegram",
            )

            assert task.name  # auto-generated
            assert task.interval_seconds == 1800
            assert task.notify_via == "telegram"
            assert task.enabled is True
            assert task.next_run_at is not None

            tasks = load_schedules()
            assert len(tasks) == 1
            assert tasks[0].name == task.name

    def test_remove_schedule(self, tmp_path):
        from alive.integrations.scheduler import (
            create_schedule,
            load_schedules,
            remove_schedule,
        )

        schedule_file = tmp_path / "schedules.json"

        with patch("alive.integrations.scheduler._schedules_path", return_value=schedule_file):
            task = create_schedule("test task", interval="1h")
            assert len(load_schedules()) == 1

            result = remove_schedule(task.name)
            assert result is True
            assert len(load_schedules()) == 0

    def test_remove_nonexistent_returns_false(self, tmp_path):
        from alive.integrations.scheduler import remove_schedule

        schedule_file = tmp_path / "schedules.json"
        schedule_file.write_text("[]")

        with patch("alive.integrations.scheduler._schedules_path", return_value=schedule_file):
            result = remove_schedule("nonexistent")
            assert result is False

    def test_pause_and_resume(self, tmp_path):
        from alive.integrations.scheduler import (
            create_schedule,
            load_schedules,
            pause_schedule,
            resume_schedule,
        )

        schedule_file = tmp_path / "schedules.json"

        with patch("alive.integrations.scheduler._schedules_path", return_value=schedule_file):
            task = create_schedule("test", interval="1h")
            assert task.enabled is True

            pause_schedule(task.name)
            tasks = load_schedules()
            assert tasks[0].enabled is False

            resume_schedule(task.name)
            tasks = load_schedules()
            assert tasks[0].enabled is True

    def test_custom_name(self, tmp_path):
        from alive.integrations.scheduler import create_schedule

        schedule_file = tmp_path / "schedules.json"

        with patch("alive.integrations.scheduler._schedules_path", return_value=schedule_file):
            task = create_schedule(
                "check weather",
                name="weather-check",
                interval="1h",
            )
            assert task.name == "weather-check"


# ── Calculate next run ─────────────────────────────────────────────────────


class TestCalculateNextRun:
    """Test next run calculation."""

    def test_interval_based(self):
        from alive.integrations.scheduler import ScheduleTask, calculate_next_run

        now = datetime(2026, 3, 18, 12, 0, 0, tzinfo=timezone.utc)
        task = ScheduleTask(
            id="test", name="test", request="test",
            interval_seconds=1800,
        )

        next_run = calculate_next_run(task, from_time=now)
        expected = (now + timedelta(seconds=1800)).isoformat()
        assert next_run == expected

    def test_time_of_day(self):
        from alive.integrations.scheduler import ScheduleTask, calculate_next_run

        now = datetime(2026, 3, 18, 6, 0, 0, tzinfo=timezone.utc)
        task = ScheduleTask(
            id="test", name="test", request="test",
            time_of_day="09:00",
        )

        next_run = calculate_next_run(task, from_time=now)
        # Should be 09:00 today (since 06:00 < 09:00)
        assert "09:00:00" in next_run

    def test_time_of_day_past(self):
        from alive.integrations.scheduler import ScheduleTask, calculate_next_run

        now = datetime(2026, 3, 18, 12, 0, 0, tzinfo=timezone.utc)
        task = ScheduleTask(
            id="test", name="test", request="test",
            time_of_day="09:00",
        )

        next_run = calculate_next_run(task, from_time=now)
        # Should be 09:00 tomorrow
        dt = datetime.fromisoformat(next_run)
        assert dt.day == 19


# ── Daemon status ──────────────────────────────────────────────────────────


class TestDaemonStatus:
    """Test daemon status checking."""

    def test_not_running_when_no_pid_file(self, tmp_path):
        from alive.integrations.scheduler import daemon_running

        with patch("alive.integrations.scheduler._pid_file", return_value=tmp_path / "daemon.pid"):
            running, pid = daemon_running()
            assert running is False
            assert pid is None

    def test_not_running_when_pid_stale(self, tmp_path):
        from alive.integrations.scheduler import daemon_running

        pid_file = tmp_path / "daemon.pid"
        pid_file.write_text("999999999")  # Very unlikely to be a real PID

        with patch("alive.integrations.scheduler._pid_file", return_value=pid_file):
            running, pid = daemon_running()
            assert running is False


# ── Load schedules edge cases ──────────────────────────────────────────────


class TestLoadSchedules:
    """Test schedule loading edge cases."""

    def test_empty_file(self, tmp_path):
        from alive.integrations.scheduler import load_schedules

        schedule_file = tmp_path / "schedules.json"
        schedule_file.write_text("[]")

        with patch("alive.integrations.scheduler._schedules_path", return_value=schedule_file):
            tasks = load_schedules()
            assert tasks == []

    def test_missing_file(self, tmp_path):
        from alive.integrations.scheduler import load_schedules

        schedule_file = tmp_path / "nonexistent.json"

        with patch("alive.integrations.scheduler._schedules_path", return_value=schedule_file):
            tasks = load_schedules()
            assert tasks == []

    def test_corrupted_file(self, tmp_path):
        from alive.integrations.scheduler import load_schedules

        schedule_file = tmp_path / "schedules.json"
        schedule_file.write_text("not json{{{")

        with patch("alive.integrations.scheduler._schedules_path", return_value=schedule_file):
            tasks = load_schedules()
            assert tasks == []


# ── ScheduleTask dataclass ─────────────────────────────────────────────────


class TestScheduleTask:
    """Test ScheduleTask defaults."""

    def test_auto_generates_id_and_timestamp(self):
        from alive.integrations.scheduler import ScheduleTask

        task = ScheduleTask(id="", name="test", request="test request")
        assert len(task.id) == 8
        assert task.created_at  # Should be auto-filled

    def test_preserves_explicit_values(self):
        from alive.integrations.scheduler import ScheduleTask

        task = ScheduleTask(
            id="custom-id",
            name="test",
            request="test",
            created_at="2026-01-01T00:00:00",
        )
        assert task.id == "custom-id"
        assert task.created_at == "2026-01-01T00:00:00"
