from __future__ import annotations

import json
from pathlib import Path
from types import SimpleNamespace

import pytest
from django.utils import timezone

from apps.nodes import tasks
from apps.screens.startup_notifications import LCD_LOW_LOCK_FILE


class DummyCache:
    def __init__(self) -> None:
        self._store: dict[str, object] = {}

    def add(self, key, value, timeout=None):  # noqa: ANN001
        if key in self._store:
            return False
        self._store[key] = value
        return True

    def delete(self, key):  # noqa: ANN001
        self._store.pop(key, None)
        return True


@pytest.fixture
def startup_cache(monkeypatch) -> DummyCache:
    cache = DummyCache()
    monkeypatch.setattr(tasks, "cache", cache)
    return cache


class DummyResponse:
    status_code = 201


class DummyNode:
    role = SimpleNamespace(name="Control", acronym="CTRL")

    def get_preferred_scheme(self) -> str:
        return "http"


@pytest.mark.django_db
def test_send_startup_net_message_writes_startup_message(
    monkeypatch, settings, tmp_path, startup_cache
):
    settings.BASE_DIR = tmp_path
    startup_cache.delete(tasks.STARTUP_NET_MESSAGE_CACHE_KEY)

    lock_dir = tmp_path / ".locks"
    lock_dir.mkdir()
    (lock_dir / "lcd_screen_enabled.lck").write_text("", encoding="utf-8")
    def write_high_lock(*, base_dir, port, lock_file=None):
        target = lock_file or (Path(base_dir) / ".locks" / tasks.LCD_HIGH_LOCK_FILE)
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text("hi\nthere\n", encoding="utf-8")
        return target

    monkeypatch.setattr(tasks.Node, "get_local", lambda: DummyNode())
    monkeypatch.setattr(tasks, "queue_startup_message", write_high_lock)

    tasks.send_startup_net_message()

    high_lines = (lock_dir / tasks.LCD_HIGH_LOCK_FILE).read_text().splitlines()
    assert high_lines == ["hi", "there"]

    assert not (lock_dir / LCD_LOW_LOCK_FILE).exists()


@pytest.mark.django_db
def test_startup_message_ignores_uptime_lock(monkeypatch, settings, tmp_path):
    settings.BASE_DIR = tmp_path
    startup_cache = DummyCache()
    monkeypatch.setattr(tasks, "cache", startup_cache)
    startup_cache.delete(tasks.STARTUP_NET_MESSAGE_CACHE_KEY)

    lock_dir = tmp_path / ".locks"
    lock_dir.mkdir()
    (lock_dir / "lcd_screen_enabled.lck").write_text("", encoding="utf-8")
    (lock_dir / "role.lck").write_text("Control", encoding="utf-8")

    started_at = timezone.now()
    (lock_dir / "suite_uptime.lck").write_text(
        json.dumps({"started_at": started_at.isoformat()}), encoding="utf-8"
    )

    def write_high_lock(*, base_dir, port, lock_file=None):
        target = lock_file or (Path(base_dir) / ".locks" / tasks.LCD_HIGH_LOCK_FILE)
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text("hi\nthere\n", encoding="utf-8")
        return target

    monkeypatch.setattr(tasks.Node, "get_local", lambda: DummyNode())
    monkeypatch.setattr(tasks, "queue_startup_message", write_high_lock)

    tasks.send_startup_net_message()

    assert not (lock_dir / LCD_LOW_LOCK_FILE).exists()


@pytest.mark.django_db
def test_startup_message_cache_resets_each_boot(
    monkeypatch, settings, tmp_path, startup_cache
):
    settings.BASE_DIR = tmp_path

    lock_dir = tmp_path / ".locks"
    lock_dir.mkdir()
    (lock_dir / "lcd_screen_enabled.lck").write_text("", encoding="utf-8")
    (lock_dir / "role.lck").write_text("Control", encoding="utf-8")

    high_payloads: list[str] = []

    def write_high_lock(*, base_dir, port, lock_file=None):
        target = lock_file or (Path(base_dir) / ".locks" / tasks.LCD_HIGH_LOCK_FILE)
        target.parent.mkdir(parents=True, exist_ok=True)
        payload = f"call-{len(high_payloads)}"
        target.write_text(f"{payload}\nbody\n", encoding="utf-8")
        high_payloads.append(payload)
        return target

    monkeypatch.setattr(tasks.django_timezone, "now", timezone.now)
    monkeypatch.setattr(tasks.Node, "get_local", lambda: DummyNode())
    monkeypatch.setattr(tasks, "queue_startup_message", write_high_lock)
    monkeypatch.setattr(tasks.psutil, "boot_time", lambda: 1000.0)

    tasks.send_startup_net_message()
    assert high_payloads == ["call-0"]
    assert not (lock_dir / LCD_LOW_LOCK_FILE).exists()

    (lock_dir / "role.lck").write_text("Terminal", encoding="utf-8")
    monkeypatch.setattr(tasks.psutil, "boot_time", lambda: 2000.0)

    tasks.send_startup_net_message()
    assert high_payloads == ["call-0", "call-1"]
    assert not (lock_dir / LCD_LOW_LOCK_FILE).exists()


@pytest.mark.django_db
def test_lcd_boot_message_avoids_database(
    monkeypatch, settings, tmp_path, django_assert_num_queries, startup_cache
):
    settings.BASE_DIR = tmp_path
    startup_cache.delete(tasks.STARTUP_NET_MESSAGE_CACHE_KEY)

    lock_dir = tmp_path / ".locks"
    lock_dir.mkdir()
    (lock_dir / "lcd_screen_enabled.lck").write_text("", encoding="utf-8")

    (lock_dir / "role.lck").write_text("Control", encoding="utf-8")

    def write_high_lock(*, base_dir, port, lock_file=None):
        target = lock_file or (Path(base_dir) / ".locks" / tasks.LCD_HIGH_LOCK_FILE)
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text("hi\nthere\n", encoding="utf-8")
        return target

    monkeypatch.setattr(tasks, "queue_startup_message", write_high_lock)

    with django_assert_num_queries(0):
        tasks.send_startup_net_message()

    assert not (lock_dir / LCD_LOW_LOCK_FILE).exists()
