"""Unit tests for worai.core.output."""

from __future__ import annotations

import csv
import io
import json
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from worai.core.output import OutputFormat, write_output

SAMPLE_ROWS = [
    {"name": "Alice", "score": 42},
    {"name": "Bob", "score": 7},
]


# ---------------------------------------------------------------------------
# table
# ---------------------------------------------------------------------------


def test_table_renders_without_error(capsys):
    write_output(SAMPLE_ROWS, OutputFormat.table)
    out = capsys.readouterr().out
    assert "Alice" in out
    assert "Bob" in out


def test_table_column_headers_present(capsys):
    write_output(SAMPLE_ROWS, OutputFormat.table)
    out = capsys.readouterr().out
    assert "name" in out
    assert "score" in out


def test_table_empty_rows_produces_no_output(capsys):
    write_output([], OutputFormat.table)
    out = capsys.readouterr().out
    assert out == ""


def test_table_writes_to_file(tmp_path):
    dest = tmp_path / "table.txt"
    write_output(SAMPLE_ROWS, OutputFormat.table, dest=dest)
    content = dest.read_text()
    assert "Alice" in content
    assert "Bob" in content


# ---------------------------------------------------------------------------
# json
# ---------------------------------------------------------------------------


def test_json_stdout_is_valid_array(capsys):
    write_output(SAMPLE_ROWS, OutputFormat.json)
    out = capsys.readouterr().out
    parsed = json.loads(out)
    assert parsed == SAMPLE_ROWS


def test_json_to_file(tmp_path):
    dest = tmp_path / "out.json"
    write_output(SAMPLE_ROWS, OutputFormat.json, dest=dest)
    parsed = json.loads(dest.read_text())
    assert parsed == SAMPLE_ROWS


# ---------------------------------------------------------------------------
# csv
# ---------------------------------------------------------------------------


def test_csv_has_header_and_rows(capsys):
    write_output(SAMPLE_ROWS, OutputFormat.csv)
    out = capsys.readouterr().out
    reader = csv.DictReader(io.StringIO(out))
    rows = list(reader)
    assert rows == [{"name": "Alice", "score": "42"}, {"name": "Bob", "score": "7"}]


def test_csv_to_file_header_and_rows(tmp_path):
    dest = tmp_path / "out.csv"
    write_output(SAMPLE_ROWS, OutputFormat.csv, dest=dest)
    reader = csv.DictReader(dest.open(newline=""))
    rows = list(reader)
    assert len(rows) == 2
    assert rows[0]["name"] == "Alice"
    assert rows[1]["score"] == "7"


def test_csv_empty_rows_writes_only_empty_header(capsys):
    write_output([], OutputFormat.csv)
    out = capsys.readouterr().out
    # No fieldnames → empty header line only
    assert out == "\r\n"


# ---------------------------------------------------------------------------
# tsv
# ---------------------------------------------------------------------------


def test_tsv_has_header_and_rows(capsys):
    write_output(SAMPLE_ROWS, OutputFormat.tsv)
    out = capsys.readouterr().out
    reader = csv.DictReader(io.StringIO(out), delimiter="\t")
    rows = list(reader)
    assert rows[0]["name"] == "Alice"
    assert rows[1]["score"] == "7"


def test_tsv_uses_tab_delimiter(capsys):
    write_output(SAMPLE_ROWS, OutputFormat.tsv)
    out = capsys.readouterr().out
    first_line = out.splitlines()[0]
    assert "\t" in first_line
    assert "," not in first_line


# ---------------------------------------------------------------------------
# parquet
# ---------------------------------------------------------------------------


def test_parquet_requires_dest():
    with pytest.raises(ValueError, match="destination file path"):
        write_output(SAMPLE_ROWS, OutputFormat.parquet)


def test_parquet_writes_readable_file(tmp_path):
    pyarrow = pytest.importorskip("pyarrow")
    import pyarrow.parquet as pq

    dest = tmp_path / "out.parquet"
    write_output(SAMPLE_ROWS, OutputFormat.parquet, dest=dest)

    table = pq.read_table(str(dest))
    assert table.num_rows == 2
    assert "name" in table.column_names
    assert "score" in table.column_names


def test_parquet_missing_extra_raises_import_error(tmp_path):
    dest = tmp_path / "out.parquet"
    with patch.dict(sys.modules, {"pyarrow": None, "pyarrow.parquet": None}):
        with pytest.raises(ImportError, match=r"worai\[parquet\]"):
            write_output(SAMPLE_ROWS, OutputFormat.parquet, dest=dest)


# ---------------------------------------------------------------------------
# gsheets
# ---------------------------------------------------------------------------


def test_gsheets_missing_credentials_raises():
    with pytest.raises(ValueError, match="credentials_file"):
        write_output(SAMPLE_ROWS, OutputFormat.gsheets, spreadsheet_id="x", sheet_name="s")


def test_gsheets_missing_spreadsheet_id_raises():
    with pytest.raises(ValueError, match="spreadsheet_id"):
        write_output(SAMPLE_ROWS, OutputFormat.gsheets, credentials_file="c.json", sheet_name="s")


def test_gsheets_missing_sheet_name_raises():
    with pytest.raises(ValueError, match="sheet_name"):
        write_output(SAMPLE_ROWS, OutputFormat.gsheets, credentials_file="c.json", spreadsheet_id="x")


def test_gsheets_clears_and_writes_header_plus_rows():
    mock_worksheet = MagicMock()
    mock_spreadsheet = MagicMock()
    mock_spreadsheet.worksheet.return_value = mock_worksheet

    mock_gc = MagicMock()
    mock_gc.open_by_key.return_value = mock_spreadsheet

    mock_gspread = MagicMock()
    mock_gspread.service_account.return_value = mock_gc

    with patch.dict(sys.modules, {"gspread": mock_gspread}):
        write_output(
            SAMPLE_ROWS,
            OutputFormat.gsheets,
            credentials_file="svc.json",
            spreadsheet_id="sheet_id",
            sheet_name="Tab1",
        )

    mock_worksheet.clear.assert_called_once()
    mock_worksheet.update.assert_called_once()

    written = mock_worksheet.update.call_args[0][0]
    assert written[0] == ["name", "score"]        # header row
    assert written[1] == ["Alice", 42]             # first data row
    assert written[2] == ["Bob", 7]                # second data row


def test_gsheets_missing_extra_raises_import_error():
    with patch.dict(sys.modules, {"gspread": None}):
        with pytest.raises(ImportError, match=r"worai\[gsheets\]"):
            write_output(
                SAMPLE_ROWS,
                OutputFormat.gsheets,
                credentials_file="c.json",
                spreadsheet_id="s",
                sheet_name="t",
            )
