import json
import os
import subprocess
import sys
from collections.abc import Callable
from pathlib import Path
from typing import Any

import pytest


@pytest.fixture
def backend_dir() -> Path:
    return Path(__file__).resolve().parents[3]


@pytest.fixture
def run_cli_command(backend_dir: Path) -> Callable[..., subprocess.CompletedProcess[str]]:
    def _run_cli_command(*args: str) -> subprocess.CompletedProcess[str]:
        return subprocess.run(
            [sys.executable, '-m', 'cli.main', *args],
            cwd=backend_dir,
            capture_output=True,
            text=True,
            check=False,
        )

    return _run_cli_command


@pytest.fixture
def run_text_cli_command(
    run_cli_command: Callable[..., subprocess.CompletedProcess[str]],
) -> Callable[..., subprocess.CompletedProcess[str]]:
    def _run_text_cli_command(*args: str) -> subprocess.CompletedProcess[str]:
        return run_cli_command('--color=never', '--icon=none', *args)

    return _run_text_cli_command


@pytest.fixture
def run_cli_completion_command(
    backend_dir: Path,
) -> Callable[..., subprocess.CompletedProcess[str]]:
    def _run_cli_completion_command(
        *,
        comp_words: str,
        comp_cword: int | str,
        instruction: str = 'bash_complete',
    ) -> subprocess.CompletedProcess[str]:
        return subprocess.run(
            [sys.executable, '-m', 'cli.main'],
            cwd=backend_dir,
            capture_output=True,
            text=True,
            check=False,
            env={
                **os.environ,
                'COMP_WORDS': comp_words,
                'COMP_CWORD': str(comp_cword),
                '_SHIERFENG_COMPLETE': instruction,
            },
        )

    return _run_cli_completion_command


@pytest.fixture
def parse_json_stdout() -> Callable[[subprocess.CompletedProcess[str]], dict[str, Any]]:
    def _parse_json_stdout(completed: subprocess.CompletedProcess[str]) -> dict[str, Any]:
        return json.loads(completed.stdout)

    return _parse_json_stdout


@pytest.fixture
def assert_check_payload_contract() -> Callable[[dict[str, Any], bool], None]:
    def _assert_check_payload_contract(payload: dict[str, Any], allow_exit_code: bool) -> None:
        assert isinstance(payload, dict)
        assert isinstance(payload.get('ok'), bool)
        assert isinstance(payload.get('message'), str)

        if payload['ok']:
            if 'error' in payload:
                assert isinstance(payload['error'], str)
            if 'exit_code' in payload:
                assert isinstance(payload['exit_code'], int)
        else:
            assert isinstance(payload.get('error'), str)
            if allow_exit_code:
                assert isinstance(payload.get('exit_code'), int)

    return _assert_check_payload_contract
