from __future__ import annotations

import asyncio
import sys
import tempfile
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path

import httpx

sys.path.insert(0, str(Path(__file__).resolve().parents[3]))

from backend.app.dependencies import get_remote_client_service
from backend.app.db.remote_client_repository import RemoteClientRepository
from backend.app.main import app
from backend.app.services.remote_client_service import RemoteClientService


class _Clock:
    def __init__(self, current: datetime):
        self.current = current

    def now(self) -> datetime:
        return self.current

    def advance(self, *, seconds: int) -> None:
        self.current += timedelta(seconds=seconds)


class RemoteClientsApiGoldenTest(unittest.TestCase):
    def setUp(self) -> None:
        self.temp_dir = tempfile.TemporaryDirectory()
        self.clock = _Clock(datetime(2026, 3, 26, 12, 0, 0, tzinfo=timezone.utc))
        repository = RemoteClientRepository(str(Path(self.temp_dir.name) / "remote-clients.db"))
        service = RemoteClientService(
            repository=repository,
            registration_token="secret-token",
            offline_timeout_seconds=60,
            now=self.clock.now,
        )

        async def _override_remote_client_service() -> RemoteClientService:
            return service

        app.dependency_overrides[get_remote_client_service] = _override_remote_client_service

    def tearDown(self) -> None:
        app.dependency_overrides.clear()
        self.temp_dir.cleanup()

    def _request(self, method: str, url: str, payload: dict | None = None, token: str | None = None) -> httpx.Response:
        async def _run() -> httpx.Response:
            transport = httpx.ASGITransport(app=app)
            headers = {}
            if token is not None:
                headers["Authorization"] = f"Bearer {token}"
            async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
                if method == "GET":
                    return await client.get(url, headers=headers)
                return await client.post(url, json=payload, headers=headers)

        return asyncio.run(_run())

    @staticmethod
    def _register_payload() -> dict:
        return {
            "client_id": "client-123",
            "display_name": "Jan MacBook",
            "platform": "macos",
            "agent_version": "1.1.0",
            "endpoint": "http://192.168.1.25:8765",
            "shares": [{"key": "downloads", "label": "Downloads"}],
        }

    def test_list_is_empty_by_default(self) -> None:
        response = self._request("GET", "/api/clients")

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json(), {"items": []})

    def test_register_then_list_then_heartbeat_and_status_timeout(self) -> None:
        register_response = self._request(
            "POST",
            "/api/clients/register",
            self._register_payload(),
            token="secret-token",
        )

        self.assertEqual(register_response.status_code, 200)
        register_body = register_response.json()
        self.assertEqual(register_body["client_id"], "client-123")
        self.assertEqual(register_body["display_name"], "Jan MacBook")
        self.assertEqual(register_body["status"], "online")
        self.assertEqual(register_body["last_seen"], "2026-03-26T12:00:00Z")
        self.assertIsNone(register_body["last_error"])
        self.assertIsNone(register_body["reachable_at"])

        list_response = self._request("GET", "/api/clients")
        self.assertEqual(list_response.status_code, 200)
        self.assertEqual(len(list_response.json()["items"]), 1)
        self.assertEqual(list_response.json()["items"][0]["status"], "online")

        self.clock.advance(seconds=30)
        heartbeat_response = self._request(
            "POST",
            "/api/clients/heartbeat",
            {"client_id": "client-123", "agent_version": "1.1.1"},
            token="secret-token",
        )
        self.assertEqual(heartbeat_response.status_code, 200)
        heartbeat_body = heartbeat_response.json()
        self.assertEqual(heartbeat_body["agent_version"], "1.1.1")
        self.assertEqual(heartbeat_body["last_seen"], "2026-03-26T12:00:30Z")
        self.assertEqual(heartbeat_body["status"], "online")

        self.clock.advance(seconds=61)
        timed_out_list = self._request("GET", "/api/clients")
        self.assertEqual(timed_out_list.status_code, 200)
        timed_out_item = timed_out_list.json()["items"][0]
        self.assertEqual(timed_out_item["status"], "offline")
        self.assertEqual(timed_out_item["last_seen"], "2026-03-26T12:00:30Z")
        self.assertIsNone(timed_out_item["last_error"])
        self.assertIsNone(timed_out_item["reachable_at"])

    def test_register_rejects_invalid_token(self) -> None:
        response = self._request(
            "POST",
            "/api/clients/register",
            self._register_payload(),
            token="wrong-token",
        )

        self.assertEqual(response.status_code, 403)
        self.assertEqual(response.json()["error"]["code"], "forbidden")


if __name__ == "__main__":
    unittest.main()
