"""
Tests für den Night Scheduler

Unit Tests für:
- Konfiguration laden/speichern
- Zeit-Parsing
- Nächste Aktion berechnen
- API Endpoints
"""

import json
import pytest
from datetime import datetime, time, timedelta
from pathlib import Path
from unittest.mock import patch, MagicMock
from fastapi.testclient import TestClient

# Importiere die zu testenden Funktionen
import sys
sys.path.insert(0, str(Path(__file__).parent.parent))

from scheduler import (
    app,
    NightModeConfig,
    NightModeStatus,
    parse_time,
    calculate_next_action,
    format_timedelta,
    load_config,
    save_config,
    CONFIG_PATH,
)


# Test Client für FastAPI
client = TestClient(app)


class TestParseTime:
    """Tests für die parse_time Funktion"""

    def test_parse_time_valid_morning(self):
        """Gültige Morgenzeit parsen"""
        result = parse_time("06:00")
        assert result.hour == 6
        assert result.minute == 0

    def test_parse_time_valid_evening(self):
        """Gültige Abendzeit parsen"""
        result = parse_time("22:30")
        assert result.hour == 22
        assert result.minute == 30

    def test_parse_time_midnight(self):
        """Mitternacht parsen"""
        result = parse_time("00:00")
        assert result.hour == 0
        assert result.minute == 0

    def test_parse_time_end_of_day(self):
        """23:59 parsen"""
        result = parse_time("23:59")
        assert result.hour == 23
        assert result.minute == 59


class TestFormatTimedelta:
    """Tests für die format_timedelta Funktion"""

    def test_format_hours_and_minutes(self):
        """Stunden und Minuten formatieren"""
        td = timedelta(hours=4, minutes=23)
        result = format_timedelta(td)
        assert result == "4h 23min"

    def test_format_only_minutes(self):
        """Nur Minuten formatieren"""
        td = timedelta(minutes=45)
        result = format_timedelta(td)
        assert result == "45min"

    def test_format_zero(self):
        """Null formatieren"""
        td = timedelta(minutes=0)
        result = format_timedelta(td)
        assert result == "0min"

    def test_format_many_hours(self):
        """Viele Stunden formatieren"""
        td = timedelta(hours=15, minutes=30)
        result = format_timedelta(td)
        assert result == "15h 30min"


class TestCalculateNextAction:
    """Tests für die calculate_next_action Funktion"""

    def test_disabled_returns_none(self):
        """Deaktivierter Modus gibt None zurück"""
        config = NightModeConfig(enabled=False)
        action, next_time, time_until = calculate_next_action(config)
        assert action is None
        assert next_time is None
        assert time_until is None

    def test_before_shutdown_time(self):
        """Vor Shutdown-Zeit: Nächste Aktion ist Shutdown"""
        config = NightModeConfig(
            enabled=True,
            shutdown_time="22:00",
            startup_time="06:00"
        )

        # Mock datetime.now() auf 18:00
        with patch('scheduler.datetime') as mock_dt:
            mock_now = datetime(2026, 2, 9, 18, 0, 0)
            mock_dt.now.return_value = mock_now
            mock_dt.combine = datetime.combine

            action, next_time, time_until = calculate_next_action(config)
            assert action == "shutdown"
            assert next_time is not None
            assert next_time.hour == 22
            assert next_time.minute == 0

    def test_after_shutdown_before_midnight(self):
        """Nach Shutdown, vor Mitternacht: Nächste Aktion ist Startup morgen"""
        config = NightModeConfig(
            enabled=True,
            shutdown_time="22:00",
            startup_time="06:00"
        )

        with patch('scheduler.datetime') as mock_dt:
            mock_now = datetime(2026, 2, 9, 23, 0, 0)
            mock_dt.now.return_value = mock_now
            mock_dt.combine = datetime.combine

            action, next_time, time_until = calculate_next_action(config)
            assert action == "startup"
            assert next_time is not None
            # Startup sollte am nächsten Tag sein
            assert next_time.day == 10

    def test_early_morning_before_startup(self):
        """Früher Morgen vor Startup: Nächste Aktion ist Startup heute"""
        config = NightModeConfig(
            enabled=True,
            shutdown_time="22:00",
            startup_time="06:00"
        )

        with patch('scheduler.datetime') as mock_dt:
            mock_now = datetime(2026, 2, 9, 4, 0, 0)
            mock_dt.now.return_value = mock_now
            mock_dt.combine = datetime.combine

            action, next_time, time_until = calculate_next_action(config)
            assert action == "startup"
            assert next_time is not None
            assert next_time.hour == 6


class TestNightModeConfig:
    """Tests für das NightModeConfig Model"""

    def test_default_config(self):
        """Standard-Konfiguration erstellen"""
        config = NightModeConfig()
        assert config.enabled is False
        assert config.shutdown_time == "22:00"
        assert config.startup_time == "06:00"
        assert config.last_action is None
        assert "night-scheduler" in config.excluded_services

    def test_config_with_values(self):
        """Konfiguration mit Werten erstellen"""
        config = NightModeConfig(
            enabled=True,
            shutdown_time="23:00",
            startup_time="07:30",
            last_action="startup",
            last_action_time="2026-02-09T07:30:00"
        )
        assert config.enabled is True
        assert config.shutdown_time == "23:00"
        assert config.startup_time == "07:30"
        assert config.last_action == "startup"


class TestAPIEndpoints:
    """Tests für die API Endpoints"""

    def test_health_endpoint(self):
        """Health Endpoint gibt Status zurück"""
        response = client.get("/health")
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "healthy"
        assert data["service"] == "night-scheduler"

    def test_get_status_endpoint(self):
        """GET /api/night-mode gibt Status zurück"""
        with patch('scheduler.load_config') as mock_load:
            mock_load.return_value = NightModeConfig()

            response = client.get("/api/night-mode")
            assert response.status_code == 200
            data = response.json()
            assert "config" in data
            assert "current_time" in data

    def test_update_config_endpoint(self):
        """POST /api/night-mode aktualisiert Konfiguration"""
        with patch('scheduler.save_config') as mock_save:
            with patch('scheduler.load_config') as mock_load:
                mock_load.return_value = NightModeConfig()

                new_config = {
                    "enabled": True,
                    "shutdown_time": "23:00",
                    "startup_time": "07:00",
                    "excluded_services": ["night-scheduler", "nginx"]
                }

                response = client.post("/api/night-mode", json=new_config)
                assert response.status_code == 200
                mock_save.assert_called_once()

    def test_update_config_invalid_time(self):
        """POST /api/night-mode mit ungültiger Zeit gibt Fehler"""
        with patch('scheduler.load_config') as mock_load:
            mock_load.return_value = NightModeConfig()

            new_config = {
                "enabled": True,
                "shutdown_time": "invalid",
                "startup_time": "06:00",
                "excluded_services": []
            }

            response = client.post("/api/night-mode", json=new_config)
            assert response.status_code == 400

    def test_execute_stop_endpoint(self):
        """POST /api/night-mode/execute mit stop"""
        with patch('scheduler.execute_docker_command') as mock_exec:
            with patch('scheduler.load_config') as mock_load:
                with patch('scheduler.save_config'):
                    mock_exec.return_value = (True, "Services gestoppt")
                    mock_load.return_value = NightModeConfig()

                    response = client.post(
                        "/api/night-mode/execute",
                        json={"action": "stop"}
                    )
                    assert response.status_code == 200
                    mock_exec.assert_called_once_with("stop")

    def test_execute_start_endpoint(self):
        """POST /api/night-mode/execute mit start"""
        with patch('scheduler.execute_docker_command') as mock_exec:
            with patch('scheduler.load_config') as mock_load:
                with patch('scheduler.save_config'):
                    mock_exec.return_value = (True, "Services gestartet")
                    mock_load.return_value = NightModeConfig()

                    response = client.post(
                        "/api/night-mode/execute",
                        json={"action": "start"}
                    )
                    assert response.status_code == 200
                    mock_exec.assert_called_once_with("start")

    def test_execute_invalid_action(self):
        """POST /api/night-mode/execute mit ungültiger Aktion"""
        response = client.post(
            "/api/night-mode/execute",
            json={"action": "invalid"}
        )
        assert response.status_code == 400

    def test_get_services_endpoint(self):
        """GET /api/night-mode/services gibt Services zurück"""
        with patch('scheduler.get_services_to_manage') as mock_services:
            with patch('scheduler.get_services_status') as mock_status:
                with patch('scheduler.load_config') as mock_load:
                    mock_services.return_value = ["backend", "frontend"]
                    mock_status.return_value = {"backend": "running", "frontend": "running"}
                    mock_load.return_value = NightModeConfig()

                    response = client.get("/api/night-mode/services")
                    assert response.status_code == 200
                    data = response.json()
                    assert "all_services" in data
                    assert "excluded_services" in data
                    assert "status" in data

    def test_get_logs_endpoint(self):
        """GET /api/night-mode/logs gibt Logs zurück"""
        with patch('scheduler.load_config') as mock_load:
            mock_load.return_value = NightModeConfig(
                last_action="shutdown",
                last_action_time="2026-02-09T22:00:00"
            )

            response = client.get("/api/night-mode/logs")
            assert response.status_code == 200
            data = response.json()
            assert data["last_action"] == "shutdown"


class TestConfigPersistence:
    """Tests für Konfigurations-Persistenz"""

    def test_load_missing_config_returns_default(self):
        """Fehlende Konfiguration gibt Standard zurück"""
        with patch.object(CONFIG_PATH, 'exists', return_value=False):
            config = load_config()
            assert config.enabled is False
            assert config.shutdown_time == "22:00"

    def test_save_and_load_config(self, tmp_path):
        """Konfiguration speichern und laden"""
        config_file = tmp_path / "night-mode.json"

        with patch('scheduler.CONFIG_PATH', config_file):
            original = NightModeConfig(
                enabled=True,
                shutdown_time="21:00",
                startup_time="05:30"
            )
            save_config(original)

            loaded = load_config()
            assert loaded.enabled == original.enabled
            assert loaded.shutdown_time == original.shutdown_time
            assert loaded.startup_time == original.startup_time


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
