"""Tests for the google_docs 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_docs"))


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_gdocs(tmp_path, transport, accounts=None):
    import fliiq.runtime.google_auth as google_auth
    from fliiq.data.skills.core.google_docs import main as gdocs

    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(gdocs, "httpx", MockHttpx):
                yield token_file


# --- Schema ---


def test_schema():
    skill = _load_skill()
    schema = skill.schema()
    assert schema["name"] == "google_docs"
    actions = schema["parameters"]["properties"]["action"]["enum"]
    assert "create" in actions
    assert "read" in actions
    assert "insert_text" in actions
    assert "batch_update" 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_docs import main as gdocs

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


# --- create ---


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

    api_response = {"documentId": "d1", "title": "Meeting Notes"}
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gdocs(tmp_path, transport):
        result = await gdocs.handler({"action": "create", "title": "Meeting Notes"})

    assert result["success"] is True
    assert result["data"]["document_id"] == "d1"
    assert result["data"]["title"] == "Meeting Notes"


# --- read ---


async def test_read(tmp_path):
    from fliiq.data.skills.core.google_docs import main as gdocs

    api_response = {
        "documentId": "d1",
        "title": "My Doc",
        "body": {
            "content": [
                {
                    "paragraph": {
                        "elements": [
                            {"textRun": {"content": "Hello "}},
                            {"textRun": {"content": "World\n"}},
                        ]
                    }
                },
                {
                    "paragraph": {
                        "elements": [
                            {"textRun": {"content": "Second paragraph\n"}},
                        ]
                    }
                },
            ]
        },
    }
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gdocs(tmp_path, transport):
        result = await gdocs.handler({"action": "read", "document_id": "d1"})

    assert result["success"] is True
    assert "Hello World" in result["data"]["text"]
    assert "Second paragraph" in result["data"]["text"]
    assert "raw" in result["data"]


async def test_read_with_table(tmp_path):
    from fliiq.data.skills.core.google_docs import main as gdocs

    api_response = {
        "documentId": "d1",
        "title": "Table Doc",
        "body": {
            "content": [
                {
                    "table": {
                        "tableRows": [
                            {
                                "tableCells": [
                                    {
                                        "content": [
                                            {
                                                "paragraph": {
                                                    "elements": [
                                                        {"textRun": {"content": "Cell A1\n"}}
                                                    ]
                                                }
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                }
            ]
        },
    }
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gdocs(tmp_path, transport):
        result = await gdocs.handler({"action": "read", "document_id": "d1"})

    assert result["success"] is True
    assert "Cell A1" in result["data"]["text"]


async def test_read_missing_id(tmp_path):
    from fliiq.data.skills.core.google_docs import main as gdocs

    transport = _mock_transport([])

    with _mock_gdocs(tmp_path, transport):
        result = await gdocs.handler({"action": "read"})

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


# --- insert_text ---


async def test_insert_text(tmp_path):
    from fliiq.data.skills.core.google_docs import main as gdocs

    captured_body = None

    def capture(request):
        nonlocal captured_body
        if request.content:
            captured_body = json.loads(request.content)
        return httpx.Response(200, json={"replies": [{}]})

    transport = httpx.MockTransport(capture)

    with _mock_gdocs(tmp_path, transport):
        result = await gdocs.handler({
            "action": "insert_text",
            "document_id": "d1",
            "text": "New text",
            "index": 5,
        })

    assert result["success"] is True
    req = captured_body["requests"][0]["insertText"]
    assert req["text"] == "New text"
    assert req["location"]["index"] == 5


async def test_insert_text_missing_params(tmp_path):
    from fliiq.data.skills.core.google_docs import main as gdocs

    transport = _mock_transport([])

    with _mock_gdocs(tmp_path, transport):
        result = await gdocs.handler({"action": "insert_text", "document_id": "d1"})

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


# --- batch_update ---


async def test_batch_update(tmp_path):
    from fliiq.data.skills.core.google_docs import main as gdocs

    api_response = {"replies": [{}, {}]}
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gdocs(tmp_path, transport):
        result = await gdocs.handler({
            "action": "batch_update",
            "document_id": "d1",
            "requests": [
                {"insertText": {"location": {"index": 1}, "text": "A"}},
                {"insertText": {"location": {"index": 2}, "text": "B"}},
            ],
        })

    assert result["success"] is True
    assert "2 update" in result["message"]


async def test_batch_update_missing_params(tmp_path):
    from fliiq.data.skills.core.google_docs import main as gdocs

    transport = _mock_transport([])

    with _mock_gdocs(tmp_path, transport):
        result = await gdocs.handler({"action": "batch_update", "document_id": "d1"})

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


# --- Unknown action ---


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

    transport = _mock_transport([])

    with _mock_gdocs(tmp_path, transport):
        result = await gdocs.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_docs import main as gdocs

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

    with _mock_gdocs(tmp_path, transport):
        result = await gdocs.handler({"action": "read", "document_id": "bad"})

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