"""``--from-fixture`` subcommand: build_plan against a JSON fixture."""

from __future__ import annotations

import json as jsonlib
from pathlib import Path
from typing import TYPE_CHECKING, cast

from django.core.management.base import CommandError

from ....models import RunTrigger
from ._fixture_client import _FixtureClient
from ._invocation import (
    _begin_cli_invocation,
    _link_invocation,
    _rate_limit_or_record,
)

if TYPE_CHECKING:
    from ....client import AuditedDiscordClient
    from ..audit_discord_roles import Command


def run_from_fixture(cmd: Command, path: str, *, json_output: bool) -> None:
    """Run build_plan against a JSON fixture; skip apply_plan."""
    # Repro entry point. The fixture-shaped client is fed into
    # the same build_plan code path real audits use, so a bug
    # in classification reproduces against the snapshot the
    # operator attached to the ticket. apply_plan is skipped:
    # the fixture is offline data, so applying would have
    # nothing to act on, and a future maintainer extending
    # apply_plan must not be tripped up by a "synthetic" run
    # making real Discord API calls.
    user, argv = _begin_cli_invocation(action_label="run audit from fixture")
    invocation = _rate_limit_or_record(user, argv)

    try:
        with Path(path).open(encoding="utf-8") as fp:
            data = jsonlib.load(fp)
    except OSError as exc:
        raise CommandError(f"failed to open fixture {path!r}: {exc}") from exc
    except jsonlib.JSONDecodeError as exc:
        raise CommandError(
            f"fixture {path!r} is not valid JSON: {exc}"
        ) from exc

    if not isinstance(data, dict):
        raise CommandError(f"fixture {path!r} root must be a JSON object")
    roles = data.get("guild_roles")
    members_pages = data.get("guild_members")
    if not isinstance(roles, list) or not isinstance(members_pages, list):
        raise CommandError(
            f"fixture {path!r} must define 'guild_roles' (list) "
            f"and 'guild_members' (list of pages)"
        )

    client = _FixtureClient(roles=roles, members_pages=members_pages)

    from ....audit import build_plan

    # Two-step cast through ``object`` because ``_FixtureClient`` is a
    # structural subset of ``AuditedDiscordClient`` (only the methods
    # ``build_plan`` reaches for); pyright refuses the direct narrowing
    # and asks for the explicit ``object`` round-trip.
    plan = build_plan(
        cast("AuditedDiscordClient", cast("object", client)),
        triggered_by=RunTrigger.CLI,
        triggered_by_user=user,
        cli_argv=argv,
    )
    _link_invocation(invocation, plan.run_id)

    if json_output:
        cmd.stdout.write(
            jsonlib.dumps(
                {
                    "run_id": plan.run_id,
                    "findings_count": len(plan.findings),
                    "fixture": path,
                    "applied": False,
                }
            )
        )
        return
    cmd.stdout.write(
        cmd.style.SUCCESS(
            f"AuditRun id={plan.run_id} created from fixture "
            f"{path!r} with {len(plan.findings)} finding(s); "
            f"apply_plan skipped."
        )
    )
