from datetime import UTC, datetime
from pathlib import Path

import pytest

from home_agent.cli.main import _format_provider_error, _handle_doctor, _print_run_summary
from home_agent.contracts.types import Item, ItemKind, RunResult
from home_agent.core.config import (
    AlertsConfig,
    AppConfig,
    BudgetConfig,
    LLMConfig,
    MemoryConfig,
    PollConfig,
    RetentionConfig,
)


def _config(db_path: Path) -> AppConfig:
    return AppConfig(
        poll=PollConfig(),
        budget=BudgetConfig(),
        retention=RetentionConfig(),
        alerts=AlertsConfig(),
        llm=LLMConfig(),
        memory=MemoryConfig(),
        enabled_providers=["gmail", "google_calendar", "graph_mail"],
        sqlite_path=db_path,
        daily_digest_enabled=True,
    )


def _email_item() -> Item:
    return Item(
        provider="gmail",
        external_id="msg-1",
        kind=ItemKind.EMAIL,
        received_at=datetime(2026, 3, 2, 10, 0, tzinfo=UTC),
        subject="Invoice ready",
        sender="billing@example.com",
        metadata={},
    )


def _calendar_item() -> Item:
    return Item(
        provider="google_calendar",
        external_id="evt-1",
        kind=ItemKind.CALENDAR_EVENT,
        received_at=datetime(2026, 3, 2, 14, 30, tzinfo=UTC),
        subject="Project sync",
        sender="calendar@example.com",
        metadata={},
    )


class FakeMailReader:
    def __init__(self, items: list[Item]) -> None:
        self._items: list[Item] = items

    def list_recent(self) -> list[Item]:
        return self._items


class FakeCalendarReader:
    def __init__(self, items: list[Item]) -> None:
        self._items: list[Item] = items

    def list_upcoming(self) -> list[Item]:
        return self._items


class FakeGmailPlugin:
    id: str = "gmail"
    version: str = "0.1.0"

    def get_mail_reader(self) -> FakeMailReader:
        return FakeMailReader([_email_item()])

    def get_calendar_reader(self) -> None:
        return None


class FakeCalendarPlugin:
    id: str = "google_calendar"
    version: str = "0.1.0"

    def get_mail_reader(self) -> None:
        return None

    def get_calendar_reader(self) -> FakeCalendarReader:
        return FakeCalendarReader([_calendar_item()])


class FailingGraphPlugin:
    id: str = "graph_mail"
    version: str = "0.1.0"

    def get_mail_reader(self) -> FakeMailReader:
        msg = "missing MICROSOFT_CLIENT_ID"
        raise RuntimeError(msg)

    def get_calendar_reader(self) -> None:
        return None


class NoReaderPlugin:
    id: str = "dummy"
    version: str = "0.1.0"

    def get_mail_reader(self) -> None:
        return None

    def get_calendar_reader(self) -> None:
        return None


def test_handle_doctor_reports_live_provider_status(
    capsys: pytest.CaptureFixture[str],
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    monkeypatch.setattr(
        "home_agent.cli.main.load_plugins",
        lambda enabled: [FakeGmailPlugin(), FakeCalendarPlugin(), FailingGraphPlugin()],
    )

    exit_code = _handle_doctor(_config(tmp_path / "test.db"))

    captured = capsys.readouterr()
    assert exit_code == 1
    assert "home-agent doctor" in captured.out
    assert "- gmail: ok (read 1 recent email(s))" in captured.out
    assert "- google_calendar: ok (read 1 upcoming event(s))" in captured.out
    assert "- graph_mail: error (missing MICROSOFT_CLIENT_ID)" in captured.out
    assert "doctor completed: providers=3 ok=2 errors=1" in captured.out


def test_handle_doctor_reports_missing_reader(
    capsys: pytest.CaptureFixture[str],
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    monkeypatch.setattr(
        "home_agent.cli.main.load_plugins",
        lambda enabled: [NoReaderPlugin()],
    )
    cfg = AppConfig(
        poll=PollConfig(),
        budget=BudgetConfig(),
        retention=RetentionConfig(),
        alerts=AlertsConfig(),
        llm=LLMConfig(),
        memory=MemoryConfig(),
        enabled_providers=["dummy"],
        sqlite_path=tmp_path / "test.db",
        daily_digest_enabled=True,
    )

    exit_code = _handle_doctor(cfg)

    captured = capsys.readouterr()
    assert exit_code == 1
    assert "- dummy: error (no diagnostic reader available)" in captured.out


def test_print_run_summary_includes_error_details(capsys: pytest.CaptureFixture[str]) -> None:
    _print_run_summary(
        RunResult(
            started_at=datetime(2026, 3, 2, 10, 0, tzinfo=UTC),
            finished_at=datetime(2026, 3, 2, 10, 1, tzinfo=UTC),
            processed=17,
            alerted=0,
            errors=["graph_mail: missing MICROSOFT_CLIENT_ID", "gmail: token expired"],
        )
    )

    captured = capsys.readouterr()
    assert "run completed: processed=17 alerted=0 errors=2" in captured.out
    assert "provider errors:" in captured.out
    assert "- graph_mail: missing MICROSOFT_CLIENT_ID" in captured.out
    assert "- gmail: token expired" in captured.out


def test_print_run_summary_omits_error_section_when_clean(
    capsys: pytest.CaptureFixture[str],
) -> None:
    _print_run_summary(
        RunResult(
            started_at=datetime(2026, 3, 2, 10, 0, tzinfo=UTC),
            finished_at=datetime(2026, 3, 2, 10, 1, tzinfo=UTC),
            processed=2,
            alerted=1,
            errors=[],
        )
    )

    captured = capsys.readouterr()
    assert "run completed: processed=2 alerted=1 errors=0" in captured.out
    assert "provider errors:" not in captured.out


def test_format_provider_error_flattens_newlines() -> None:
    exc = RuntimeError("line one\nline two")
    assert _format_provider_error(exc) == "line one line two"


def test_format_provider_error_falls_back_to_exception_name() -> None:
    class BlankError(Exception):
        pass

    assert _format_provider_error(BlankError()) == "BlankError"
