"""Tests for the google_slides 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_slides"))


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

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


# --- Schema ---


def test_schema():
    skill = _load_skill()
    schema = skill.schema()
    assert schema["name"] == "google_slides"
    actions = schema["parameters"]["properties"]["action"]["enum"]
    assert "create" in actions
    assert "read" in actions
    assert "add_slide" 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_slides import main as gslides

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


# --- create ---


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

    api_response = {"presentationId": "p1", "title": "Q1 Review"}
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gslides(tmp_path, transport):
        result = await gslides.handler({"action": "create", "title": "Q1 Review"})

    assert result["success"] is True
    assert result["data"]["presentation_id"] == "p1"
    assert result["data"]["title"] == "Q1 Review"


# --- read ---


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

    api_response = {
        "presentationId": "p1",
        "title": "My Deck",
        "slides": [
            {
                "objectId": "slide1",
                "pageElements": [
                    {
                        "objectId": "shape1",
                        "shape": {
                            "shapeType": "TEXT_BOX",
                            "text": {
                                "textElements": [
                                    {"textRun": {"content": "Title Slide"}},
                                ]
                            },
                        },
                    }
                ],
            }
        ],
    }
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gslides(tmp_path, transport):
        result = await gslides.handler({"action": "read", "presentation_id": "p1"})

    assert result["success"] is True
    assert len(result["data"]["slides"]) == 1
    assert "Title Slide" in result["data"]["slides"][0]["text"]
    assert result["data"]["slides"][0]["shapes"][0]["type"] == "TEXT_BOX"


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

    transport = _mock_transport([])

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

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


# --- add_slide ---


async def test_add_slide(tmp_path):
    from fliiq.data.skills.core.google_slides import main as gslides

    captured_body = None

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

    transport = httpx.MockTransport(capture)

    with _mock_gslides(tmp_path, transport):
        result = await gslides.handler({
            "action": "add_slide",
            "presentation_id": "p1",
            "layout": "TITLE_AND_BODY",
        })

    assert result["success"] is True
    assert result["data"]["slide_id"] == "new_slide_1"
    req = captured_body["requests"][0]["createSlide"]
    assert req["slideLayoutReference"]["predefinedLayout"] == "TITLE_AND_BODY"


async def test_add_slide_missing_id(tmp_path):
    from fliiq.data.skills.core.google_slides import main as gslides

    transport = _mock_transport([])

    with _mock_gslides(tmp_path, transport):
        result = await gslides.handler({"action": "add_slide"})

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


# --- insert_text ---


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

    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_gslides(tmp_path, transport):
        result = await gslides.handler({
            "action": "insert_text",
            "presentation_id": "p1",
            "shape_id": "shape1",
            "text": "Hello Slides",
        })

    assert result["success"] is True
    req = captured_body["requests"][0]["insertText"]
    assert req["objectId"] == "shape1"
    assert req["text"] == "Hello Slides"


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

    transport = _mock_transport([])

    with _mock_gslides(tmp_path, transport):
        result = await gslides.handler({
            "action": "insert_text",
            "presentation_id": "p1",
        })

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


# --- batch_update ---


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

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

    with _mock_gslides(tmp_path, transport):
        result = await gslides.handler({
            "action": "batch_update",
            "presentation_id": "p1",
            "requests": [
                {"createSlide": {"slideLayoutReference": {"predefinedLayout": "BLANK"}}},
                {"deleteObject": {"objectId": "old_slide"}},
            ],
        })

    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_slides import main as gslides

    transport = _mock_transport([])

    with _mock_gslides(tmp_path, transport):
        result = await gslides.handler({"action": "batch_update", "presentation_id": "p1"})

    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_slides import main as gslides

    transport = _mock_transport([])

    with _mock_gslides(tmp_path, transport):
        result = await gslides.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_slides import main as gslides

    transport = _mock_transport([
        httpx.Response(403, json={"error": {"message": "Access denied"}}),
    ])

    with _mock_gslides(tmp_path, transport):
        result = await gslides.handler({"action": "read", "presentation_id": "bad"})

    assert result["success"] is False
    assert "403" in result["message"]
    assert "Access denied" in result["message"]
