import json
import socket

import pytest
from unittest.mock import patch, MagicMock

from runtime.cli import cmd_pipe
from runtime.config import RuntimeConfig


@pytest.fixture
def mock_config(tmp_path):
    return RuntimeConfig(data_dir=tmp_path, local_port=8003)


def test_cmd_pipe_basic_stdin(capsys, mock_config):
    """Mock stdin with 3 lines, mock /chat responses, assert 3 output lines."""
    stdin_text = "line one\nline two\nline three\n"

    responses = [
        {"success": True, "steps": [{"content": "resp 1", "model": "claude"}]},
        {"success": True, "steps": [{"content": "resp 2", "model": "claude"}]},
        {"success": True, "steps": [{"content": "resp 3", "model": "claude"}]},
    ]

    with patch("sys.stdin.isatty", return_value=False), \
         patch("sys.stdin.read", return_value=stdin_text), \
         patch("runtime.cli._is_runtime_running", return_value=True), \
         patch("runtime.cli._runtime_post", side_effect=responses) as mock_post:

        args = MagicMock()
        args.model = None
        args.strategy = None
        args.json = False
        args.format = None
        args.delimiter = "\n"
        args.batch_size = 1
        args.timeout = 120

        result = cmd_pipe(args, mock_config)

    assert result == 0
    assert mock_post.call_count == 3

    captured = capsys.readouterr()
    lines = [l for l in captured.out.strip().split("\n") if l]
    assert len(lines) == 3
    assert lines[0] == "resp 1"
    assert lines[1] == "resp 2"
    assert lines[2] == "resp 3"


def test_cmd_pipe_json_flag(capsys, mock_config):
    """Run cmd_pipe with --json flag and assert each line is valid JSON with expected keys."""
    stdin_text = "hello\nworld\n"

    responses = [
        {"success": True, "steps": [{"content": "hi", "model": "claude-sonnet", "tokens": 42}]},
        {"success": True, "steps": [{"content": "earth", "model": "llama3", "tokens": 17}]},
    ]

    monotonic_values = iter([0.0, 0.150, 0.150, 0.400])

    with patch("sys.stdin.isatty", return_value=False), \
         patch("sys.stdin.read", return_value=stdin_text), \
         patch("runtime.cli._is_runtime_running", return_value=True), \
         patch("runtime.cli._runtime_post", side_effect=responses), \
         patch("runtime.cli.time.monotonic", side_effect=monotonic_values):

        args = MagicMock()
        args.model = None
        args.strategy = None
        args.json = True
        args.format = None
        args.delimiter = "\n"
        args.batch_size = 1
        args.timeout = 120

        result = cmd_pipe(args, mock_config)

    assert result == 0

    captured = capsys.readouterr()
    lines = [l for l in captured.out.strip().split("\n") if l]
    assert len(lines) == 2

    expected_keys = {"input", "output", "model", "tokens", "latency_ms"}

    row0 = json.loads(lines[0])
    assert set(row0.keys()) == expected_keys
    assert row0["input"] == "hello"
    assert row0["output"] == "hi"
    assert row0["model"] == "claude-sonnet"
    assert row0["tokens"] == 42
    assert row0["latency_ms"] == 150

    row1 = json.loads(lines[1])
    assert set(row1.keys()) == expected_keys
    assert row1["input"] == "world"
    assert row1["output"] == "earth"
    assert row1["model"] == "llama3"
    assert row1["tokens"] == 17
    assert row1["latency_ms"] == 250


def test_cmd_pipe_timeout(capsys, mock_config):
    """Run cmd_pipe with --timeout 1, mock POST to raise Timeout, assert error JSON."""
    stdin_text = "hello\n"

    with patch("sys.stdin.isatty", return_value=False), \
         patch("sys.stdin.read", return_value=stdin_text), \
         patch("runtime.cli._is_runtime_running", return_value=True), \
         patch("runtime.cli._runtime_post", side_effect=socket.timeout("timed out")):

        args = MagicMock()
        args.model = None
        args.strategy = None
        args.json = False
        args.format = None
        args.delimiter = "\n"
        args.batch_size = 1
        args.timeout = 1

        result = cmd_pipe(args, mock_config)

    captured = capsys.readouterr()
    lines = [l for l in captured.out.strip().split("\n") if l]
    assert len(lines) == 1

    row = json.loads(lines[0])
    assert "timeout" in row.get("error", "")
    assert row["input"] == "hello"
    assert result == 1


def test_cmd_pipe_csv_format(capsys, mock_config):
    """Run cmd_pipe with --format csv and assert output lines contain commas as delimiters."""
    stdin_text = "hello\nworld\n"

    responses = [
        {"success": True, "steps": [{"content": "hi there", "model": "claude-sonnet", "tokens": 10}]},
        {"success": True, "steps": [{"content": "the earth", "model": "llama3", "tokens": 20}]},
    ]

    monotonic_values = iter([0.0, 0.5, 1.0, 1.5])

    with patch("sys.stdin.isatty", return_value=False), \
         patch("sys.stdin.read", return_value=stdin_text), \
         patch("runtime.cli._is_runtime_running", return_value=True), \
         patch("runtime.cli._runtime_post", side_effect=responses), \
         patch("runtime.cli.time.monotonic", side_effect=monotonic_values):

        args = MagicMock()
        args.model = None
        args.strategy = None
        args.json = False
        args.format = "csv"
        args.delimiter = "\n"
        args.batch_size = 1
        args.timeout = 120

        result = cmd_pipe(args, mock_config)

    assert result == 0

    captured = capsys.readouterr()
    lines = [l for l in captured.out.strip().split("\n") if l]

    # Each chunk produces a header + values line → 4 lines total
    assert len(lines) == 4

    # First chunk: header + values
    header0 = lines[0].split(",")
    values0 = lines[1].split(",")
    assert header0 == ["input", "output", "model", "tokens", "latency_ms"]
    assert values0[0] == "hello"
    assert values0[1] == "hi there"
    assert values0[2] == "claude-sonnet"
    assert values0[3] == "10"
    assert values0[4] == "500"

    # Second chunk: header + values
    header1 = lines[2].split(",")
    values1 = lines[3].split(",")
    assert header1 == ["input", "output", "model", "tokens", "latency_ms"]
    assert values1[0] == "world"
    assert values1[1] == "the earth"
    assert values1[2] == "llama3"
    assert values1[3] == "20"
    assert values1[4] == "500"

    # Verify every line uses commas as delimiters
    for line in lines:
        assert "," in line
