"""Tests for the google_sheets skill."""

import json
import os
import time
from contextlib import contextmanager
from unittest.mock import patch

import httpx
import pytest

from fliiq.runtime.package_data import bundled_skills_dir
from fliiq.runtime.skills.base import SkillBase

SKILLS_DIR = str(bundled_skills_dir())

_RealAsyncClient = httpx.AsyncClient


def _load_skill() -> SkillBase:
    return SkillBase(os.path.join(SKILLS_DIR, "google_sheets"))


def _write_tokens(tmp_path, accounts=None):
    if accounts is None:
        accounts = {
            "test@gmail.com": {
                "access_token": "valid_token",
                "refresh_token": "refresh_tok",
                "expires_at": time.time() + 3600,
            }
        }
    token_file = tmp_path / "google_tokens.json"
    token_file.write_text(json.dumps(accounts))
    return token_file


def _mock_transport(responses: list[httpx.Response]):
    idx = 0

    def handler(request):
        nonlocal idx
        if idx < len(responses):
            resp = responses[idx]
            idx += 1
            return resp
        return httpx.Response(500, json={"error": "No more mocked responses"})

    return httpx.MockTransport(handler)


@contextmanager
def _mock_gsheets(tmp_path, transport, accounts=None):
    import fliiq.runtime.google_auth as google_auth
    from fliiq.data.skills.core.google_sheets import main as gsheets

    token_file = _write_tokens(tmp_path, accounts)

    def _make_client(**kw):
        kw["transport"] = transport
        return _RealAsyncClient(**kw)

    class MockHttpx:
        AsyncClient = staticmethod(_make_client)
        HTTPStatusError = httpx.HTTPStatusError

    with patch.object(google_auth, "TOKENS_PATH", token_file):
        with patch.object(google_auth, "httpx", MockHttpx):
            with patch.object(gsheets, "httpx", MockHttpx):
                yield token_file


# --- Schema ---


def test_schema():
    skill = _load_skill()
    schema = skill.schema()
    assert schema["name"] == "google_sheets"
    actions = schema["parameters"]["properties"]["action"]["enum"]
    assert "create" in actions
    assert "read_range" in actions
    assert "write_range" in actions
    assert "append_rows" in actions


# --- Missing credentials ---


async def test_missing_token_file(tmp_path):
    import fliiq.runtime.google_auth as google_auth
    from fliiq.data.skills.core.google_sheets import main as gsheets

    with patch.object(google_auth, "TOKENS_PATH", tmp_path / "nonexistent.json"):
        with pytest.raises(ValueError, match="fliiq google auth"):
            await gsheets.handler({"action": "create"})


# --- create ---


async def test_create(tmp_path):
    from fliiq.data.skills.core.google_sheets import main as gsheets

    api_response = {
        "spreadsheetId": "s1",
        "properties": {"title": "Budget 2026"},
        "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/s1",
    }
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gsheets(tmp_path, transport):
        result = await gsheets.handler({"action": "create", "title": "Budget 2026"})

    assert result["success"] is True
    assert result["data"]["spreadsheet_id"] == "s1"
    assert result["data"]["title"] == "Budget 2026"


# --- get_metadata ---


async def test_get_metadata(tmp_path):
    from fliiq.data.skills.core.google_sheets import main as gsheets

    api_response = {
        "spreadsheetId": "s1",
        "properties": {"title": "Budget"},
        "sheets": [
            {
                "properties": {
                    "title": "Sheet1",
                    "sheetId": 0,
                    "gridProperties": {"rowCount": 100, "columnCount": 26},
                }
            }
        ],
    }
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gsheets(tmp_path, transport):
        result = await gsheets.handler({"action": "get_metadata", "spreadsheet_id": "s1"})

    assert result["success"] is True
    assert len(result["data"]["sheets"]) == 1
    assert result["data"]["sheets"][0]["title"] == "Sheet1"


async def test_get_metadata_missing_id(tmp_path):
    from fliiq.data.skills.core.google_sheets import main as gsheets

    transport = _mock_transport([])

    with _mock_gsheets(tmp_path, transport):
        result = await gsheets.handler({"action": "get_metadata"})

    assert result["success"] is False
    assert "spreadsheet_id" in result["message"]


# --- read_range ---


async def test_read_range(tmp_path):
    from fliiq.data.skills.core.google_sheets import main as gsheets

    api_response = {
        "range": "Sheet1!A1:B2",
        "values": [["Name", "Age"], ["Alice", "30"]],
    }
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gsheets(tmp_path, transport):
        result = await gsheets.handler({
            "action": "read_range",
            "spreadsheet_id": "s1",
            "range": "Sheet1!A1:B2",
        })

    assert result["success"] is True
    assert result["data"]["values"] == [["Name", "Age"], ["Alice", "30"]]


async def test_read_range_missing_params(tmp_path):
    from fliiq.data.skills.core.google_sheets import main as gsheets

    transport = _mock_transport([])

    with _mock_gsheets(tmp_path, transport):
        result = await gsheets.handler({"action": "read_range", "spreadsheet_id": "s1"})

    assert result["success"] is False
    assert "range" in result["message"]


# --- write_range ---


async def test_write_range(tmp_path):
    from fliiq.data.skills.core.google_sheets import main as gsheets

    captured_body = None

    def capture(request):
        nonlocal captured_body
        if request.content:
            captured_body = json.loads(request.content)
        return httpx.Response(200, json={
            "updatedRange": "Sheet1!A1:B2",
            "updatedRows": 2,
            "updatedColumns": 2,
            "updatedCells": 4,
        })

    transport = httpx.MockTransport(capture)

    with _mock_gsheets(tmp_path, transport):
        result = await gsheets.handler({
            "action": "write_range",
            "spreadsheet_id": "s1",
            "range": "Sheet1!A1:B2",
            "values": [["Name", "Age"], ["Alice", "30"]],
        })

    assert result["success"] is True
    assert result["data"]["updated_cells"] == 4
    assert captured_body["values"] == [["Name", "Age"], ["Alice", "30"]]


# --- append_rows ---


async def test_append_rows(tmp_path):
    from fliiq.data.skills.core.google_sheets import main as gsheets

    api_response = {
        "updates": {
            "updatedRange": "Sheet1!A3:B3",
            "updatedRows": 1,
            "updatedCells": 2,
        }
    }
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gsheets(tmp_path, transport):
        result = await gsheets.handler({
            "action": "append_rows",
            "spreadsheet_id": "s1",
            "range": "Sheet1!A:B",
            "values": [["Bob", "25"]],
        })

    assert result["success"] is True
    assert result["data"]["updated_rows"] == 1


# --- Unknown action ---


async def test_unknown_action(tmp_path):
    from fliiq.data.skills.core.google_sheets import main as gsheets

    transport = _mock_transport([])

    with _mock_gsheets(tmp_path, transport):
        result = await gsheets.handler({"action": "invalid"})

    assert result["success"] is False
    assert "Unknown action" in result["message"]


# --- API error ---


async def test_api_error(tmp_path):
    from fliiq.data.skills.core.google_sheets import main as gsheets

    transport = _mock_transport([
        httpx.Response(404, json={"error": {"message": "Spreadsheet not found"}}),
    ])

    with _mock_gsheets(tmp_path, transport):
        result = await gsheets.handler({"action": "read_range", "spreadsheet_id": "bad", "range": "A1"})

    assert result["success"] is False
    assert "404" in result["message"]
    assert "Spreadsheet not found" in result["message"]
