from __future__ import annotations
import datetime
import json
from pathlib import Path
from typer.testing import CliRunner
from qrm import cli
from qrm.client import LoginState

runner = CliRunner()


"""
Feature: lr-qrm operator workflows
    Scenario: Check service status
        Given an authenticated engineer
        When they call `qrm status`
        Then they see the service health response.

    Scenario: Show skill
        Given the CLI is installed
        When I run `qrm --show-skill`
        Then SKILL.md content is printed to stdout

    Scenario: Install skill
        Given the CLI is installed
        When I run `qrm --install-skill`
        Then the skill is copied to the target directory

    Scenario: Mutually exclusive skill flags
        Given the CLI is installed
        When I run `qrm --install-skill --show-skill`
        Then exit code 2 is returned
"""

import importlib
import json
from importlib import metadata as importlib_metadata
from importlib.metadata import PackageNotFoundError
from pathlib import Path

from typer.testing import CliRunner

from qrm import __version__
from qrm import cli
from qrm import uut_commands

runner = CliRunner()


def test_status_command_calls_api(monkeypatch, tmp_path: Path) -> None:
    """Status should load stored credentials and print the API response."""

    from qrm.client import LoginState

    state = LoginState(  # type: ignore[call-arg]
        token="TOKEN123",
        username="alice",
        base_url="https://qrm.local/api",
        verify_tls=True,
    )

    config_path = tmp_path / "login.json"
    config_path.write_text(state.model_dump_json())

    captured = {}

    class FakeClient:
        def __init__(self, base_url: str, verify_tls: bool) -> None:  # noqa: D401
            captured["base_url"] = base_url
            captured["verify_tls"] = verify_tls

        def status(self, token: str | None = None):  # noqa: D401
            captured["token"] = token
            return {"healthy": True}

    monkeypatch.setattr(cli, "QrmClient", FakeClient)

    result = runner.invoke(
        cli.app,
        ["status", "--config-path", str(config_path)],
    )

    assert result.exit_code == 0
    assert "healthy" in result.stdout
    assert "True" in result.stdout
    assert captured["token"] == "TOKEN123"
    assert captured["base_url"] == "https://qrm.local/api"
    assert captured["verify_tls"] is False


def test_status_command_supports_json_output(monkeypatch, tmp_path: Path) -> None:
    """Status should emit raw JSON when requested."""

    from qrm.client import LoginState

    state = LoginState(  # type: ignore[call-arg]
        token="TOKEN123",
        username="alice",
        base_url="https://qrm.local/api",
        verify_tls=True,
    )
    config_path = tmp_path / "login.json"
    config_path.write_text(state.model_dump_json())

    class FakeClient:
        def __init__(self, *_: str, **__: str) -> None:  # noqa: D401, ANN001, ANN002, ANN003
            pass

        def status(self, token: str | None = None):  # noqa: D401
            return {"healthy": True, "token": token}

    monkeypatch.setattr(cli, "QrmClient", FakeClient)

    result = runner.invoke(
        cli.app,
        ["status", "--config-path", str(config_path), "--output", "json"],
    )

    assert result.exit_code == 0
    assert json.loads(result.stdout) == {"healthy": True, "token": "TOKEN123"}


def test_status_fails_when_config_missing(tmp_path: Path) -> None:
    """Status should exit with code 1 when config file is missing."""

    missing_path = tmp_path / "nope.json"

    result = runner.invoke(cli.app, ["status", "--config-path", str(missing_path)])

    assert result.exit_code == 1
    assert "No login state found" in result.stdout


def test_status_surfaces_client_error(monkeypatch, tmp_path: Path) -> None:
    """Status should surface QrmClientError nicely."""

    from qrm.client import LoginState

    state = LoginState(  # type: ignore[call-arg]
        token="TOKEN123",
        username="alice",
        base_url="https://qrm.local/api",
        verify_tls=True,
    )
    config_path = tmp_path / "login.json"
    config_path.write_text(state.model_dump_json())

    class ExplodingClient:
        def __init__(self, *_: str, **__: str) -> None:  # noqa: D401
            pass

        def status(self, *_: str, **__: str):
            raise cli.QrmClientError("kaboom")

    monkeypatch.setattr(cli, "QrmClient", ExplodingClient)

    result = runner.invoke(
        cli.app,
        ["status", "--config-path", str(config_path)],
    )

    assert result.exit_code == 1
    assert "Status check failed" in result.stdout


def test_status_renders_list_payload(monkeypatch, tmp_path: Path) -> None:
    """Non-dict payloads should still print via console."""

    from qrm.client import LoginState

    state = LoginState(  # type: ignore[call-arg]
        token="TOKEN123",
        username="alice",
        base_url="https://qrm.local/api",
        verify_tls=True,
    )
    config_path = tmp_path / "login.json"
    config_path.write_text(state.model_dump_json())

    class FakeClient:
        def __init__(self, *_: str, **__: str) -> None:  # noqa: D401
            pass

        def status(self, token: str | None = None):
            return ["ok", token]

    monkeypatch.setattr(cli, "QrmClient", FakeClient)

    result = runner.invoke(
        cli.app,
        ["status", "--config-path", str(config_path)],
    )

    assert result.exit_code == 0
    assert "ok" in result.stdout


def test_cli_displays_help_when_no_command() -> None:
    """Invoking the CLI without arguments returns the Typer help text."""

    result = runner.invoke(cli.app, [])

    assert result.exit_code == 0
    assert "Usage" in result.stdout


def test_version_option_returns_package_version() -> None:
    """`qrm --version` surfaces the packaged version string."""

    result = runner.invoke(cli.app, ["--version"])

    assert result.exit_code == 0
    assert __version__ in result.stdout


def test_version_fallback_reads_pyproject(monkeypatch) -> None:
    """If the package metadata is unavailable, fallback parser should work."""

    def raise_not_found(*_, **__) -> str:  # noqa: ANN002, ANN003
        raise PackageNotFoundError

    monkeypatch.setattr(importlib_metadata, "version", raise_not_found)

    import qrm as qrm_module

    reloaded = importlib.reload(qrm_module)

    assert reloaded._load_version() == __version__


def test_login_command_persists_token(monkeypatch, tmp_path):
    """The login command stores the returned token to disk."""

    captured = {}

    class FakeClient:
        def __init__(self, base_url: str, verify_tls: bool) -> None:  # noqa: D401
            captured["base_url"] = base_url
            captured["verify_tls"] = verify_tls

        def login(self, username: str, password: str) -> str:  # noqa: D401
            captured["username"] = username
            captured["password"] = password
            return "TOKEN123"

    monkeypatch.setattr(cli, "QrmClient", FakeClient)

    config_path = tmp_path / "login.json"
    result = runner.invoke(
        cli.app,
        [
            "login",
            "--username",
            "alice",
            "--password",
            "wonderland",
            "--base-url",
            "https://qrm.local/api",
            "--config-path",
            str(config_path),
        ],
    )

    assert result.exit_code == 0
    data = json.loads(config_path.read_text())
    assert data["token"] == "TOKEN123"
    assert data["username"] == "alice"
    assert data["base_url"] == "https://qrm.local/api"
    assert data["verify_tls"] is True
    assert captured["verify_tls"] is True


def test_login_command_handles_client_errors(monkeypatch, tmp_path):
    """Failures surface as a helpful error with exit code 1."""

    class ExplodingClient:
        def __init__(self, *_, **__):  # noqa: D401, ANN002, ANN003
            pass

        def login(self, *_: str, **__: str) -> str:  # noqa: ANN001, ANN002, ANN003
            raise cli.QrmClientError("boom")

    monkeypatch.setattr(uut_commands, "QrmClient", ExplodingClient)

    config_path = tmp_path / "login.json"
    result = runner.invoke(
        cli.app,
        [
            "login",
            "--username",
            "alice",
            "--password",
            "wonderland",
            "--config-path",
            str(config_path),
        ],
    )

    assert result.exit_code == 1
    assert "Login failed" in result.stdout
    assert not config_path.exists()


def test_show_skill():
    """qrm --show-skill prints SKILL.md content."""
    result = runner.invoke(cli.app, ["--show-skill"])
    assert result.exit_code == 0
    assert "lr-qrm" in result.stdout


def test_install_skill(tmp_path, monkeypatch):
    """qrm --install-skill copies the skill to ~/.claude/skills/."""
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    monkeypatch.chdir(tmp_path)
    result = runner.invoke(cli.app, ["--install-skill"])
    assert result.exit_code == 0
    assert (tmp_path / ".claude" / "skills" / "lr-qrm").is_dir()


def test_skill_flags_mutually_exclusive():
    """--install-skill and --show-skill together return exit code 2."""
    result = runner.invoke(cli.app, ["--install-skill", "--show-skill"])
    assert result.exit_code == 2


# ---------------------------------------------------------------------------
# query test-duration
# ---------------------------------------------------------------------------

from qrm import query_commands


def _make_config(tmp_path: Path, base_url: str = "https://qrm.local/api") -> Path:
    from qrm.client import LoginState

    state = LoginState(
        token="TOKEN123",
        username="alice",
        base_url=base_url,
        verify_tls=True,
    )
    config_path = tmp_path / "login.json"
    config_path.write_text(state.model_dump_json())
    return config_path


def _agg_rows():
    """Pre-aggregated rows as returned by the SQL GROUP BY query."""
    return [
        {
            "StationName": "RYW1036",
            "SequenceName": "AirGlow test",
            "Count": 3,
            "MinDur": 5,
            "AvgDur": 53.67,
            "MaxDur": 90,
            "ShortCount": 1,
        }
    ]


class _FakeClient:
    def __init__(self, rows):
        self._rows = rows

    def export_sql(self, **_):
        return self._rows


def test_query_test_duration_rich(monkeypatch, tmp_path: Path) -> None:
    """test-duration renders a rich table with statistical columns."""
    config_path = _make_config(tmp_path)
    monkeypatch.setattr(query_commands, "QrmClient", lambda **_: _FakeClient(_agg_rows()))
    result = runner.invoke(
        cli.app,
        [
            "query",
            "test-duration",
            "--config-path",
            str(config_path),
            "--start",
            "2026-03-20T00:00:00",
            "--stop",
            "2026-03-26T23:59:59",
        ],
    )
    assert result.exit_code == 0
    assert "Test Duration" in result.stdout
    assert "RYW1036" in result.stdout
    assert "53.7" in result.stdout  # avg duration


def test_query_test_duration_json(monkeypatch, tmp_path: Path) -> None:
    """test-duration --output json returns envelope with start/stop/rows."""
    config_path = _make_config(tmp_path)
    monkeypatch.setattr(query_commands, "QrmClient", lambda **_: _FakeClient(_agg_rows()))
    result = runner.invoke(
        cli.app,
        [
            "query",
            "test-duration",
            "--config-path",
            str(config_path),
            "--start",
            "2026-03-20T00:00:00",
            "--stop",
            "2026-03-26T23:59:59",
            "--output",
            "json",
        ],
    )
    assert result.exit_code == 0
    data = json.loads(result.stdout)
    assert "start" in data
    assert "stop" in data
    assert isinstance(data["rows"], list)
    assert len(data["rows"]) == 1
    row = data["rows"][0]
    assert row["StationName"] == "RYW1036"
    assert row["Count"] == 3
    assert row["ShortCount"] == 1


def test_query_test_duration_empty(monkeypatch, tmp_path: Path) -> None:
    """test-duration with no data shows 'No results'."""
    config_path = _make_config(tmp_path)
    monkeypatch.setattr(query_commands, "QrmClient", lambda **_: _FakeClient([]))
    result = runner.invoke(
        cli.app,
        [
            "query",
            "test-duration",
            "--config-path",
            str(config_path),
            "--start",
            "1999-01-01T00:00:00",
            "--stop",
            "1999-01-01T23:59:59",
        ],
    )
    assert result.exit_code == 0
    # Rich wraps "No results" across rows when columns are narrow; check for table title instead
    assert "Test Duration" in result.stdout


def test_query_test_duration_invalid_date(monkeypatch, tmp_path: Path) -> None:
    """test-duration with an invalid date exits with code 1."""
    config_path = _make_config(tmp_path)
    result = runner.invoke(
        cli.app,
        [
            "query",
            "test-duration",
            "--config-path",
            str(config_path),
            "--start",
            "not-a-date",
        ],
    )
    assert result.exit_code == 1
    assert "Invalid date format" in result.stdout


def test_query_test_duration_client_error(monkeypatch, tmp_path: Path) -> None:
    """test-duration propagates QrmClientError with exit code 1."""
    from qrm.client import QrmClientError

    config_path = _make_config(tmp_path)

    class _Exploding:
        def export_sql(self, **_):
            raise QrmClientError("boom")

    monkeypatch.setattr(query_commands, "QrmClient", lambda **_: _Exploding())
    result = runner.invoke(
        cli.app,
        [
            "query",
            "test-duration",
            "--config-path",
            str(config_path),
            "--start",
            "2026-03-20T00:00:00",
            "--stop",
            "2026-03-26T23:59:59",
        ],
    )
    assert result.exit_code == 1
    assert "query failed" in result.stdout


def test_render_test_duration_short_count_flag() -> None:
    """render_test_duration highlights ShortCount > 0 in bold red markup."""
    from qrm.commands_shared import render_test_duration

    rows = [
        {
            "StationName": "RYW1036",
            "SequenceName": "AirGlow test",
            "Count": 100,
            "MinDur": 5,
            "AvgDur": 74.0,
            "MaxDur": 110,
            "ShortCount": 3,
        }
    ]
    table = render_test_duration(rows)
    # The table object has the row data; verify ShortCount markup was applied
    cell = table.columns[-1]._cells[0]
    assert "red" in cell or "3" in cell
