"""Unit tests for UpdatesAvailableCheck and UpdatesDigestCheck."""
from __future__ import annotations

from pathlib import Path
from unittest.mock import AsyncMock, MagicMock

import pytest

from ha_diag.checks.updates_available import (
    UpdatesAvailableCheck,
    UpdatesDigestCheck,
    _build_update_payload,
)
from ha_diag.config import Settings
from ha_diag.models import HAEventType
from ha_diag.storage import Storage


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_settings(**overrides) -> Settings:
    defaults: dict = {
        "ha_url": "http://test.local:8123",
        "ha_token": "test",
        "node_name": "test-node",
        "location_tag": "test-loc",
        "alert_cooldown_hours": 0.0,
        "updates_cooldown_days": 0,   # no dedup in most tests
        "check_interval": 60,
        "check_interval_unavailable": 3600,
    }
    defaults.update(overrides)
    return Settings(**defaults)


def _make_client(states=None, error=None):
    client = MagicMock()
    if error:
        client.get_states = AsyncMock(side_effect=error)
    else:
        client.get_states = AsyncMock(return_value=states or [])
    return client


def _update_state(
    entity_id: str = "update.homeassistant_core",
    state: str = "on",
    title: str = "Home Assistant Core",
    installed: str = "2025.5.0",
    latest: str = "2025.6.0",
    release_summary: str | None = None,
    release_url: str | None = None,
) -> dict:
    attrs: dict = {
        "title": title,
        "installed_version": installed,
        "latest_version": latest,
        "in_progress": False,
        "auto_update": False,
    }
    if release_summary:
        attrs["release_summary"] = release_summary
    if release_url:
        attrs["release_url"] = release_url
    return {"entity_id": entity_id, "state": state, "attributes": attrs}


# ---------------------------------------------------------------------------
# _build_update_payload helper
# ---------------------------------------------------------------------------


def test_build_update_payload_basic():
    attrs = {"title": "HA Core", "installed_version": "1.0", "latest_version": "2.0"}
    p = _build_update_payload("update.ha_core", attrs)
    assert p["entity_id"] == "update.ha_core"
    assert p["title"] == "HA Core"
    assert p["installed_version"] == "1.0"
    assert p["latest_version"] == "2.0"


def test_build_update_payload_release_summary_truncated():
    long_notes = "x" * 3000
    attrs = {"release_summary": long_notes}
    p = _build_update_payload("update.ha_core", attrs)
    assert len(p["release_summary"]) == 2000


def test_build_update_payload_release_url_omitted_when_absent():
    p = _build_update_payload("update.ha_core", {})
    assert "release_url" not in p


def test_build_update_payload_release_url_included_when_present():
    attrs = {"release_url": "https://github.com/..."}
    p = _build_update_payload("update.x", attrs)
    assert p["release_url"] == "https://github.com/..."


# ---------------------------------------------------------------------------
# UpdatesAvailableCheck (daily individual events)
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_no_updates_returns_empty(storage: Storage):
    states = [{"entity_id": "light.living_room", "state": "on", "attributes": {}}]
    check = UpdatesAvailableCheck(_make_client(states), storage, _make_settings())
    assert await check.run() == []


@pytest.mark.asyncio
async def test_update_off_state_not_emitted(storage: Storage):
    states = [_update_state(state="off")]
    check = UpdatesAvailableCheck(_make_client(states), storage, _make_settings())
    assert await check.run() == []


@pytest.mark.asyncio
async def test_single_update_emits_event(storage: Storage):
    states = [_update_state()]
    check = UpdatesAvailableCheck(_make_client(states), storage, _make_settings())
    results = await check.run()
    assert len(results) == 1
    assert results[0].event_type == HAEventType.ha_update_available
    assert "2025.5.0" in results[0].message
    assert "2025.6.0" in results[0].message


@pytest.mark.asyncio
async def test_multiple_updates_emit_multiple_events(storage: Storage):
    states = [
        _update_state("update.ha_core"),
        _update_state("update.mosquitto", title="Mosquitto"),
    ]
    check = UpdatesAvailableCheck(_make_client(states), storage, _make_settings())
    results = await check.run()
    assert len(results) == 2
    assert all(r.event_type == HAEventType.ha_update_available for r in results)


@pytest.mark.asyncio
async def test_cooldown_prevents_same_update_next_day(storage: Storage):
    states = [_update_state()]
    settings = _make_settings(updates_cooldown_days=7)
    check = UpdatesAvailableCheck(_make_client(states), storage, settings)
    r1 = await check.run()
    r2 = await check.run()
    assert len(r1) == 1
    assert r2 == []


@pytest.mark.asyncio
async def test_no_cooldown_allows_repeat(storage: Storage):
    states = [_update_state()]
    check = UpdatesAvailableCheck(_make_client(states), storage, _make_settings(updates_cooldown_days=0))
    r1 = await check.run()
    r2 = await check.run()
    assert len(r1) == 1
    assert len(r2) == 1


@pytest.mark.asyncio
async def test_payload_contains_version_fields(storage: Storage):
    states = [_update_state(installed="2025.5.0", latest="2025.6.0")]
    check = UpdatesAvailableCheck(_make_client(states), storage, _make_settings())
    results = await check.run()
    p = results[0].payload
    assert p["installed_version"] == "2025.5.0"
    assert p["latest_version"] == "2025.6.0"
    assert p["in_progress"] is False


@pytest.mark.asyncio
async def test_ha_error_returns_empty(storage: Storage):
    check = UpdatesAvailableCheck(
        _make_client(error=ConnectionError("HA down")), storage, _make_settings()
    )
    assert await check.run() == []


# ---------------------------------------------------------------------------
# UpdatesDigestCheck (Sunday digest)
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_digest_no_updates_returns_empty(storage: Storage):
    check = UpdatesDigestCheck(_make_client([]), storage, _make_settings())
    assert await check.run() == []


@pytest.mark.asyncio
async def test_digest_emits_single_event_for_all_updates(storage: Storage):
    states = [
        _update_state("update.ha_core"),
        _update_state("update.mosquitto", title="Mosquitto"),
        _update_state("update.esphome", title="ESPHome"),
    ]
    check = UpdatesDigestCheck(_make_client(states), storage, _make_settings())
    results = await check.run()
    assert len(results) == 1
    p = results[0].payload
    assert p["digest"] is True
    assert p["count"] == 3
    assert len(p["updates"]) == 3


@pytest.mark.asyncio
async def test_digest_payload_has_digest_true(storage: Storage):
    states = [_update_state()]
    check = UpdatesDigestCheck(_make_client(states), storage, _make_settings())
    results = await check.run()
    assert results[0].payload["digest"] is True


@pytest.mark.asyncio
async def test_digest_weekly_dedup_prevents_same_week_refiring(storage: Storage):
    states = [_update_state()]
    check = UpdatesDigestCheck(_make_client(states), storage, _make_settings())
    r1 = await check.run()
    r2 = await check.run()
    assert len(r1) == 1
    assert r2 == []


@pytest.mark.asyncio
async def test_digest_fires_independently_of_daily_dedup(storage: Storage):
    """Daily cooldown on entity X doesn't suppress Sunday digest."""
    states = [_update_state()]
    settings = _make_settings(updates_cooldown_days=7)

    # Daily check marks alert_key="update_available:update.homeassistant_core"
    daily = UpdatesAvailableCheck(_make_client(states), storage, settings)
    await daily.run()

    # Digest uses different key "update_digest:{week}" — should still fire
    digest = UpdatesDigestCheck(_make_client(states), storage, settings)
    r = await digest.run()
    assert len(r) == 1
    assert r[0].payload["digest"] is True


@pytest.mark.asyncio
async def test_digest_name_is_updates_digest(storage: Storage):
    check = UpdatesDigestCheck(_make_client([]), storage, _make_settings())
    assert check.name == "updates_digest"


@pytest.mark.asyncio
async def test_daily_check_name_is_updates_available(storage: Storage):
    check = UpdatesAvailableCheck(_make_client([]), storage, _make_settings())
    assert check.name == "updates_available"
