"""Tests for foreground app trigger provider."""

from __future__ import annotations

import sys
import types
from collections.abc import Callable

import pytest

from noteflow.domain.triggers.entities import TriggerSource
from noteflow.infrastructure.triggers.foreground_app import (
    ForegroundAppProvider,
    ForegroundAppSettings,
)


class DummyWindow:
    """Mock window object for pywinctl tests."""

    def __init__(self, title: str | None) -> None:
        self.title = title


class _PyWinCtlModule(types.ModuleType):
    """Typed pywinctl module stub for tests."""

    getActiveWindow: Callable[[], DummyWindow | None]


def _install_pywinctl(monkeypatch: pytest.MonkeyPatch, title: str | None) -> None:
    """Install mocked pywinctl with specified window title."""
    window = DummyWindow(title) if title is not None else None
    module = _PyWinCtlModule("pywinctl")
    module.getActiveWindow = lambda: window
    monkeypatch.setitem(sys.modules, "pywinctl", module)


def _settings(
    *,
    enabled: bool = True,
    weight: float = 0.4,
    meeting_apps: set[str] | None = None,
    suppressed_apps: set[str] | None = None,
) -> ForegroundAppSettings:
    """Create ForegroundAppSettings with defaults and overrides."""
    return ForegroundAppSettings(
        enabled=enabled,
        weight=weight,
        meeting_apps=meeting_apps or {"zoom"},
        suppressed_apps=suppressed_apps or set(),
    )


# --- Existing Tests ---


def test_foreground_app_provider_emits_signal(monkeypatch: pytest.MonkeyPatch) -> None:
    """Provider emits signal when a meeting app is in foreground."""
    _install_pywinctl(monkeypatch, "Zoom Meeting")
    provider = ForegroundAppProvider(_settings())

    signal = provider.get_signal()

    assert signal is not None, "Signal should be emitted when meeting app is in foreground"
    assert abs(signal.weight - 0.4) < 1e-9, "Signal weight should match configured weight"
    assert signal.app_name == "Zoom Meeting", "Signal app_name should match window title"


def test_foreground_app_provider_suppressed(monkeypatch: pytest.MonkeyPatch) -> None:
    """Suppressed apps should not emit signals."""
    _install_pywinctl(monkeypatch, "Zoom Meeting")
    provider = ForegroundAppProvider(_settings(suppressed_apps={"zoom"}))

    assert provider.get_signal() is None, "Suppressed app should not emit signal"


def test_foreground_app_provider_unavailable(monkeypatch: pytest.MonkeyPatch) -> None:
    """Unavailable provider should report disabled."""
    provider = ForegroundAppProvider(_settings())
    monkeypatch.setattr(provider, "_is_available", lambda: False)

    assert provider.is_enabled() is False, "Unavailable provider should report disabled"


# --- New Tests ---


def test_foreground_app_provider_source_property() -> None:
    """Provider source should be FOREGROUND_APP."""
    provider = ForegroundAppProvider(_settings())
    assert provider.source == TriggerSource.FOREGROUND_APP, "Provider source should be FOREGROUND_APP"


def test_foreground_app_provider_max_weight_property() -> None:
    """Provider max_weight should reflect configured weight."""
    provider = ForegroundAppProvider(_settings(weight=0.5))
    assert abs(provider.max_weight - 0.5) < 1e-9, "max_weight should match configured weight"


def test_foreground_app_settings_lowercases_apps() -> None:
    """Settings __post_init__ should lowercase meeting_apps and suppressed_apps."""
    settings = ForegroundAppSettings(
        enabled=True,
        weight=0.4,
        meeting_apps={"ZOOM", "Teams", "GoToMeeting"},
        suppressed_apps={"SLACK", "Discord"},
    )

    assert "zoom" in settings.meeting_apps, "ZOOM should be lowercased to zoom"
    assert "teams" in settings.meeting_apps, "Teams should be lowercased to teams"
    assert "gotomeeting" in settings.meeting_apps, "GoToMeeting should be lowercased to gotomeeting"
    assert "slack" in settings.suppressed_apps, "SLACK should be lowercased to slack"
    assert "discord" in settings.suppressed_apps, "Discord should be lowercased to discord"
    # Original case should not be present
    assert "ZOOM" not in settings.meeting_apps, "Original uppercase ZOOM should not be present"
    assert "SLACK" not in settings.suppressed_apps, "Original uppercase SLACK should not be present"


def test_foreground_app_provider_disabled_returns_none(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Provider should return None when enabled=False."""
    _install_pywinctl(monkeypatch, "Zoom Meeting")
    provider = ForegroundAppProvider(_settings(enabled=False))

    assert provider.get_signal() is None, "Disabled provider should return None"


def _make_is_available_true() -> bool:
    """Return True for _is_available mock."""
    return True


def test_foreground_app_provider_no_window_returns_none(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Provider should return None when getActiveWindow() returns None."""
    _install_pywinctl(monkeypatch, None)
    provider = ForegroundAppProvider(_settings())
    # Force availability check to succeed via method patch
    monkeypatch.setattr(provider, "_is_available", _make_is_available_true)

    assert provider.get_signal() is None, "No active window should return None"


def test_foreground_app_provider_empty_title_returns_none(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Provider should return None when window title is empty string."""
    _install_pywinctl(monkeypatch, "")
    provider = ForegroundAppProvider(_settings())
    monkeypatch.setattr(provider, "_is_available", _make_is_available_true)

    assert provider.get_signal() is None, "Empty window title should return None"


def test_foreground_app_provider_non_meeting_app_returns_none(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Provider should return None when foreground app is not a meeting app."""
    _install_pywinctl(monkeypatch, "Firefox Browser")
    provider = ForegroundAppProvider(_settings(meeting_apps={"zoom", "teams"}))
    monkeypatch.setattr(provider, "_is_available", _make_is_available_true)

    assert provider.get_signal() is None, "Non-meeting app should return None"


def test_foreground_app_provider_suppress_app() -> None:
    """suppress_app should add lowercased app to suppressed_apps."""
    provider = ForegroundAppProvider(_settings(suppressed_apps=set()))

    provider.suppress_app("ZOOM")
    provider.suppress_app("Teams")

    assert "zoom" in provider.suppressed_apps, "ZOOM should be suppressed as lowercase zoom"
    assert "teams" in provider.suppressed_apps, "Teams should be suppressed as lowercase teams"


def test_foreground_app_provider_unsuppress_app() -> None:
    """unsuppress_app should remove app from suppressed_apps."""
    provider = ForegroundAppProvider(_settings(suppressed_apps={"zoom", "teams"}))

    provider.unsuppress_app("zoom")

    assert "zoom" not in provider.suppressed_apps, "zoom should be removed from suppressed_apps"
    assert "teams" in provider.suppressed_apps, "teams should still be suppressed"


def test_foreground_app_provider_add_meeting_app() -> None:
    """add_meeting_app should add lowercased app to meeting_apps."""
    provider = ForegroundAppProvider(_settings(meeting_apps={"zoom"}))

    provider.add_meeting_app("WEBEX")
    provider.add_meeting_app("RingCentral")

    assert "webex" in provider.meeting_apps, "WEBEX should be added as lowercase webex"
    assert "ringcentral" in provider.meeting_apps, "RingCentral should be added as lowercase ringcentral"


def test_foreground_app_provider_suppressed_apps_property() -> None:
    """suppressed_apps property should return frozenset."""
    provider = ForegroundAppProvider(_settings(suppressed_apps={"zoom", "teams"}))

    result = provider.suppressed_apps

    assert isinstance(result, frozenset), "suppressed_apps should return frozenset"
    assert "zoom" in result, "zoom should be in suppressed_apps"
    assert "teams" in result, "teams should be in suppressed_apps"


def test_foreground_app_provider_case_insensitive_matching(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Provider should match meeting apps case-insensitively."""
    _install_pywinctl(monkeypatch, "ZOOM MEETING - Conference Room")
    provider = ForegroundAppProvider(_settings(meeting_apps={"zoom"}))
    monkeypatch.setattr(provider, "_is_available", _make_is_available_true)

    signal = provider.get_signal()

    assert signal is not None, "Case-insensitive match should emit signal"
    assert signal.app_name == "ZOOM MEETING - Conference Room", "app_name should preserve original title case"


def test_foreground_app_provider_is_enabled_when_enabled_and_available(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """is_enabled should return True when both enabled and available."""
    _install_pywinctl(monkeypatch, "Some Window")
    provider = ForegroundAppProvider(_settings(enabled=True))

    assert provider.is_enabled() is True, "Provider should be enabled when configured enabled and available"
