"""
Tests fuer vast.ai Power Control API.

Testet die FastAPI Endpoints fuer Start/Stop/Status.
"""

import json
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from datetime import datetime, timezone
from pathlib import Path
import tempfile
import os

# Setze ENV vor jedem Import
os.environ["VAST_API_KEY"] = "test-api-key"
os.environ["VAST_INSTANCE_ID"] = "12345"
os.environ["CONTROL_API_KEY"] = "test-control-key"

from fastapi.testclient import TestClient
from fastapi import FastAPI


class TestVastState:
    """Tests fuer VastState Klasse."""

    def test_load_empty_state(self):
        """Test leerer State wird erstellt."""
        with tempfile.TemporaryDirectory() as tmpdir:
            state_path = Path(tmpdir) / "state.json"
            os.environ["VAST_STATE_PATH"] = str(state_path)

            # Importiere nach ENV-Setup
            from infra.vast_power import VastState

            state = VastState(path=state_path)

            assert state.get("desired_state") is None
            assert state.get("total_runtime_seconds") == 0

    def test_set_and_get(self):
        """Test Wert setzen und lesen."""
        with tempfile.TemporaryDirectory() as tmpdir:
            state_path = Path(tmpdir) / "state.json"

            from infra.vast_power import VastState

            state = VastState(path=state_path)
            state.set("desired_state", "RUNNING")

            assert state.get("desired_state") == "RUNNING"
            assert state_path.exists()

    def test_record_activity(self):
        """Test Aktivitaet aufzeichnen."""
        with tempfile.TemporaryDirectory() as tmpdir:
            state_path = Path(tmpdir) / "state.json"

            from infra.vast_power import VastState

            state = VastState(path=state_path)
            state.record_activity()

            last = state.get_last_activity()
            assert last is not None
            assert isinstance(last, datetime)

    def test_record_start_stop_calculates_cost(self):
        """Test Start/Stop berechnet Kosten."""
        with tempfile.TemporaryDirectory() as tmpdir:
            state_path = Path(tmpdir) / "state.json"

            from infra.vast_power import VastState

            state = VastState(path=state_path)

            # Simuliere Start
            state.record_start()
            assert state.get("desired_state") == "RUNNING"

            # Simuliere Stop mit Kosten ($0.50/h)
            state.record_stop(dph_total=0.5)
            assert state.get("desired_state") == "STOPPED"
            assert state.get("total_runtime_seconds") > 0


class TestAuditLog:
    """Tests fuer Audit Logging."""

    def test_audit_log_writes(self):
        """Test Audit Log schreibt Eintraege."""
        with tempfile.TemporaryDirectory() as tmpdir:
            audit_path = Path(tmpdir) / "audit.log"

            # Importiere und patche AUDIT_PATH direkt
            import infra.vast_power as vp
            original_path = vp.AUDIT_PATH
            vp.AUDIT_PATH = audit_path

            try:
                vp.audit_log("test_event", actor="test_user", meta={"key": "value"})

                assert audit_path.exists()
                content = audit_path.read_text()
                entry = json.loads(content.strip())

                assert entry["event"] == "test_event"
                assert entry["actor"] == "test_user"
                assert entry["meta"]["key"] == "value"
            finally:
                vp.AUDIT_PATH = original_path


class TestPowerEndpointsAuth:
    """Tests fuer Authentifizierung der Power Endpoints."""

    def test_require_control_key_no_key_configured(self):
        """Test Fehler wenn CONTROL_API_KEY nicht gesetzt."""
        import infra.vast_power as vp
        from fastapi import HTTPException

        # Temporaer CONTROL_API_KEY leeren
        original = vp.CONTROL_API_KEY
        vp.CONTROL_API_KEY = None

        try:
            with pytest.raises(HTTPException) as exc_info:
                vp.require_control_key("any-key")
            assert exc_info.value.status_code == 500
            assert "not configured" in str(exc_info.value.detail)
        finally:
            vp.CONTROL_API_KEY = original

    def test_require_control_key_wrong_key(self):
        """Test 401 bei falschem Key."""
        import infra.vast_power as vp
        from fastapi import HTTPException

        # Setze gueltigen CONTROL_API_KEY
        original = vp.CONTROL_API_KEY
        vp.CONTROL_API_KEY = "correct-key"

        try:
            with pytest.raises(HTTPException) as exc_info:
                vp.require_control_key("wrong-key")
            assert exc_info.value.status_code == 401
        finally:
            vp.CONTROL_API_KEY = original

    def test_require_control_key_valid(self):
        """Test kein Fehler bei korrektem Key."""
        import infra.vast_power as vp

        # Setze gueltigen CONTROL_API_KEY
        original = vp.CONTROL_API_KEY
        vp.CONTROL_API_KEY = "my-secret-key"

        try:
            # Sollte keine Exception werfen
            result = vp.require_control_key("my-secret-key")
            assert result is None  # Dependency gibt nichts zurueck
        finally:
            vp.CONTROL_API_KEY = original

    def test_require_control_key_none_provided(self):
        """Test 401 wenn kein Key im Header."""
        import infra.vast_power as vp
        from fastapi import HTTPException

        original = vp.CONTROL_API_KEY
        vp.CONTROL_API_KEY = "valid-key"

        try:
            with pytest.raises(HTTPException) as exc_info:
                vp.require_control_key(None)
            assert exc_info.value.status_code == 401
        finally:
            vp.CONTROL_API_KEY = original


class TestStatusEndpoint:
    """Tests fuer den Status Endpoint."""

    def test_status_response_model(self):
        """Test VastStatusResponse Model Validierung."""
        from infra.vast_power import VastStatusResponse

        # Unconfigured response
        resp = VastStatusResponse(status="unconfigured", message="Not configured")
        assert resp.status == "unconfigured"
        assert resp.instance_id is None

        # Running response
        resp = VastStatusResponse(
            instance_id=12345,
            status="running",
            gpu_name="RTX 3090",
            dph_total=0.45,
            endpoint_base_url="http://10.0.0.1:8001",
            auto_shutdown_in_minutes=25,
        )
        assert resp.instance_id == 12345
        assert resp.status == "running"
        assert resp.gpu_name == "RTX 3090"

    def test_status_returns_instance_info(self):
        """Test Status gibt korrektes Modell zurueck."""
        from infra.vast_client import InstanceInfo, InstanceStatus
        from infra.vast_power import VastStatusResponse

        # Simuliere was der Endpoint zurueckgibt
        mock_instance = InstanceInfo(
            id=12345,
            status=InstanceStatus.RUNNING,
            gpu_name="RTX 3090",
            dph_total=0.45,
            public_ipaddr="10.0.0.1",
        )

        # Baue Response wie der Endpoint es tun wuerde
        endpoint = mock_instance.get_endpoint_url(8001)
        response = VastStatusResponse(
            instance_id=mock_instance.id,
            status=mock_instance.status.value,
            gpu_name=mock_instance.gpu_name,
            dph_total=mock_instance.dph_total,
            endpoint_base_url=endpoint,
        )

        assert response.instance_id == 12345
        assert response.status == "running"
        assert response.gpu_name == "RTX 3090"
        assert response.dph_total == 0.45


class TestActivityEndpoint:
    """Tests fuer den Activity Endpoint."""

    def test_record_activity_updates_state(self):
        """Test Activity wird im State aufgezeichnet."""
        with tempfile.TemporaryDirectory() as tmpdir:
            from infra.vast_power import VastState

            state_path = Path(tmpdir) / "state.json"
            state = VastState(path=state_path)

            # Keine Aktivitaet vorher
            assert state.get_last_activity() is None

            # Aktivitaet aufzeichnen
            state.record_activity()

            # Jetzt sollte Aktivitaet vorhanden sein
            last = state.get_last_activity()
            assert last is not None
            assert isinstance(last, datetime)


class TestCostsEndpoint:
    """Tests fuer den Costs Endpoint."""

    def test_costs_response_model(self):
        """Test CostStatsResponse Model."""
        from infra.vast_power import CostStatsResponse

        resp = CostStatsResponse(
            total_runtime_hours=2.5,
            total_cost_usd=1.25,
            sessions_count=3,
            avg_session_minutes=50.0,
        )

        assert resp.total_runtime_hours == 2.5
        assert resp.total_cost_usd == 1.25
        assert resp.sessions_count == 3


class TestAuditEndpoint:
    """Tests fuer den Audit Log Endpoint."""

    def test_audit_entries_parsed(self):
        """Test Audit Log Eintraege werden geparst."""
        with tempfile.TemporaryDirectory() as tmpdir:
            audit_path = Path(tmpdir) / "audit.log"

            # Schreibe Test-Eintraege
            entries = [
                '{"ts": "2024-01-15T10:00:00Z", "event": "power_on", "actor": "admin", "meta": {}}',
                '{"ts": "2024-01-15T11:00:00Z", "event": "power_off", "actor": "admin", "meta": {}}',
            ]
            audit_path.write_text("\n".join(entries))

            # Lese und parse
            lines = audit_path.read_text().strip().split("\n")
            parsed = [json.loads(line) for line in lines]

            assert len(parsed) == 2
            assert parsed[0]["event"] == "power_on"
            assert parsed[1]["event"] == "power_off"


class TestRequestModels:
    """Tests fuer Request/Response Models."""

    def test_power_on_request_defaults(self):
        """Test PowerOnRequest Defaults."""
        from infra.vast_power import PowerOnRequest

        req = PowerOnRequest()
        assert req.wait_for_health is True
        assert req.health_path == "/health"
        assert req.health_port == 8001

    def test_power_on_request_custom(self):
        """Test PowerOnRequest Custom Werte."""
        from infra.vast_power import PowerOnRequest

        req = PowerOnRequest(
            wait_for_health=False,
            health_path="/v1/models",
            health_port=8000,
        )
        assert req.wait_for_health is False
        assert req.health_path == "/v1/models"
        assert req.health_port == 8000

    def test_vast_status_response(self):
        """Test VastStatusResponse Model."""
        from infra.vast_power import VastStatusResponse

        resp = VastStatusResponse(
            instance_id=12345,
            status="running",
            gpu_name="RTX 3090",
            dph_total=0.5,
        )

        assert resp.instance_id == 12345
        assert resp.status == "running"
        assert resp.auto_shutdown_in_minutes is None

    def test_power_off_response(self):
        """Test PowerOffResponse Model."""
        from infra.vast_power import PowerOffResponse

        resp = PowerOffResponse(
            status="stopped",
            session_runtime_minutes=30.5,
            session_cost_usd=0.25,
        )

        assert resp.status == "stopped"
        assert resp.session_runtime_minutes == 30.5
        assert resp.session_cost_usd == 0.25

    def test_vast_status_response_with_budget(self):
        """Test VastStatusResponse mit Budget-Feldern."""
        from infra.vast_power import VastStatusResponse

        resp = VastStatusResponse(
            instance_id=12345,
            status="running",
            gpu_name="RTX 3090",
            dph_total=0.186,
            account_credit=23.86,
            account_total_spend=1.19,
            session_runtime_minutes=120.5,
            session_cost_usd=0.37,
        )

        assert resp.instance_id == 12345
        assert resp.status == "running"
        assert resp.account_credit == 23.86
        assert resp.account_total_spend == 1.19
        assert resp.session_runtime_minutes == 120.5
        assert resp.session_cost_usd == 0.37

    def test_vast_status_response_budget_none(self):
        """Test VastStatusResponse ohne Budget (API nicht erreichbar)."""
        from infra.vast_power import VastStatusResponse

        resp = VastStatusResponse(
            instance_id=12345,
            status="running",
            account_credit=None,
            account_total_spend=None,
            session_runtime_minutes=None,
            session_cost_usd=None,
        )

        assert resp.account_credit is None
        assert resp.account_total_spend is None
        assert resp.session_runtime_minutes is None
        assert resp.session_cost_usd is None


class TestSessionCostCalculation:
    """Tests fuer Session-Kosten Berechnung."""

    def test_session_cost_calculation_basic(self):
        """Test grundlegende Session-Kosten Berechnung."""
        # Formel: (runtime_minutes / 60) * dph_total
        runtime_minutes = 60.0  # 1 Stunde
        dph_total = 0.186  # $0.186/h

        session_cost = (runtime_minutes / 60) * dph_total

        assert abs(session_cost - 0.186) < 0.001

    def test_session_cost_calculation_partial_hour(self):
        """Test Session-Kosten fuer halbe Stunde."""
        runtime_minutes = 30.0  # 30 min
        dph_total = 0.5  # $0.50/h

        session_cost = (runtime_minutes / 60) * dph_total

        assert abs(session_cost - 0.25) < 0.001  # $0.25

    def test_session_cost_calculation_multi_hour(self):
        """Test Session-Kosten fuer mehrere Stunden."""
        runtime_minutes = 240.0  # 4 Stunden
        dph_total = 0.186  # $0.186/h

        session_cost = (runtime_minutes / 60) * dph_total

        assert abs(session_cost - 0.744) < 0.001  # $0.744

    def test_session_cost_zero_runtime(self):
        """Test Session-Kosten bei null Laufzeit."""
        runtime_minutes = 0.0
        dph_total = 0.5

        session_cost = (runtime_minutes / 60) * dph_total

        assert session_cost == 0.0

    def test_session_cost_zero_dph(self):
        """Test Session-Kosten bei null Stundensatz (sollte nie passieren)."""
        runtime_minutes = 60.0
        dph_total = 0.0

        session_cost = (runtime_minutes / 60) * dph_total

        assert session_cost == 0.0


class TestBudgetWarningLevels:
    """Tests fuer Budget-Warnlevel (UI verwendet diese)."""

    def test_budget_critical_threshold(self):
        """Test Budget unter $5 ist kritisch (rot)."""
        credit = 4.99
        assert credit < 5  # Kritisch

    def test_budget_warning_threshold(self):
        """Test Budget zwischen $5 und $15 ist Warnung (orange)."""
        credit = 10.0
        assert credit >= 5 and credit < 15  # Warnung

    def test_budget_ok_threshold(self):
        """Test Budget ueber $15 ist OK (gruen)."""
        credit = 23.86
        assert credit >= 15  # OK


class TestSessionRecoveryAfterRestart:
    """Tests fuer Session-Recovery nach Container-Neustart."""

    def test_state_without_last_start(self):
        """Test State ohne last_start (nach Neustart)."""
        with tempfile.TemporaryDirectory() as tmpdir:
            from infra.vast_power import VastState

            state_path = Path(tmpdir) / "state.json"
            state = VastState(path=state_path)

            # Kein last_start sollte None sein
            assert state.get("last_start") is None

    def test_state_preserves_last_start(self):
        """Test State speichert last_start korrekt."""
        with tempfile.TemporaryDirectory() as tmpdir:
            from infra.vast_power import VastState

            state_path = Path(tmpdir) / "state.json"
            state = VastState(path=state_path)

            # Setze last_start
            test_time = "2025-12-16T10:00:00+00:00"
            state.set("last_start", test_time)

            # Erstelle neuen State-Objekt (simuliert Neustart)
            state2 = VastState(path=state_path)

            assert state2.get("last_start") == test_time

    def test_state_uses_instance_start_date(self):
        """Test dass Instance start_date verwendet werden kann."""
        from infra.vast_client import InstanceInfo, InstanceStatus
        from datetime import datetime, timezone

        # Simuliere Instance mit start_date
        instance = InstanceInfo(
            id=12345,
            status=InstanceStatus.RUNNING,
            started_at=datetime(2025, 12, 16, 10, 0, 0, tzinfo=timezone.utc),
        )

        assert instance.started_at is not None
        assert instance.started_at.isoformat() == "2025-12-16T10:00:00+00:00"
