"""Tests for the google_drive 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_drive"))


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

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


# --- Schema ---


def test_schema():
    skill = _load_skill()
    schema = skill.schema()
    assert schema["name"] == "google_drive"
    assert "action" in schema["parameters"]["properties"]
    assert schema["parameters"]["required"] == ["action"]
    actions = schema["parameters"]["properties"]["action"]["enum"]
    assert "list_files" in actions
    assert "upload_file" in actions
    assert "export_file" in actions
    assert "delete_file" 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_drive import main as gdrive

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


# --- list_files ---


async def test_list_files(tmp_path):
    from fliiq.data.skills.core.google_drive import main as gdrive

    api_response = {
        "files": [
            {"id": "f1", "name": "doc.txt", "mimeType": "text/plain", "modifiedTime": "2026-01-01T00:00:00Z"},
            {"id": "f2", "name": "sheet.xlsx", "mimeType": "application/vnd.google-apps.spreadsheet"},
        ]
    }
    transport = _mock_transport([httpx.Response(200, json=api_response)])

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({"action": "list_files"})

    assert result["success"] is True
    assert len(result["data"]["files"]) == 2
    assert result["data"]["files"][0]["name"] == "doc.txt"


async def test_list_files_custom_folder(tmp_path):
    from fliiq.data.skills.core.google_drive import main as gdrive

    captured_url = None

    def capture(request):
        nonlocal captured_url
        captured_url = str(request.url)
        return httpx.Response(200, json={"files": []})

    transport = httpx.MockTransport(capture)

    with _mock_gdrive(tmp_path, transport):
        await gdrive.handler({"action": "list_files", "folder_id": "abc123"})

    assert "abc123" in captured_url


# --- search_files ---


async def test_search_files(tmp_path):
    from fliiq.data.skills.core.google_drive import main as gdrive

    captured_url = None

    def capture(request):
        nonlocal captured_url
        captured_url = str(request.url)
        return httpx.Response(200, json={"files": [{"id": "f1", "name": "budget.xlsx"}]})

    transport = httpx.MockTransport(capture)

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({"action": "search_files", "query": "name contains 'budget'"})

    assert result["success"] is True
    assert len(result["data"]["files"]) == 1
    assert "budget" in captured_url


async def test_search_files_missing_query(tmp_path):
    from fliiq.data.skills.core.google_drive import main as gdrive

    transport = _mock_transport([])

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({"action": "search_files"})

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


# --- get_metadata ---


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

    metadata = {"id": "f1", "name": "report.pdf", "mimeType": "application/pdf", "size": "1024"}
    transport = _mock_transport([httpx.Response(200, json=metadata)])

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({"action": "get_metadata", "file_id": "f1"})

    assert result["success"] is True
    assert result["data"]["name"] == "report.pdf"


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

    transport = _mock_transport([])

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

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


# --- create_folder ---


async def test_create_folder(tmp_path):
    from fliiq.data.skills.core.google_drive import main as gdrive

    captured_body = None

    def capture(request):
        nonlocal captured_body
        if request.method == "POST":
            captured_body = json.loads(request.content)
        return httpx.Response(200, json={"id": "folder1", "name": "New Folder"})

    transport = httpx.MockTransport(capture)

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({"action": "create_folder", "file_name": "New Folder"})

    assert result["success"] is True
    assert result["data"]["id"] == "folder1"
    assert captured_body["mimeType"] == "application/vnd.google-apps.folder"


# --- upload_file ---


async def test_upload_file(tmp_path):
    from fliiq.data.skills.core.google_drive import main as gdrive

    captured_content_type = None

    def capture(request):
        nonlocal captured_content_type
        captured_content_type = request.headers.get("content-type", "")
        return httpx.Response(200, json={"id": "uploaded1", "name": "test.txt"})

    transport = httpx.MockTransport(capture)

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({
            "action": "upload_file",
            "file_name": "test.txt",
            "file_content": "Hello world",
        })

    assert result["success"] is True
    assert result["data"]["id"] == "uploaded1"
    assert "multipart/related" in captured_content_type


async def test_upload_file_missing_params(tmp_path):
    from fliiq.data.skills.core.google_drive import main as gdrive

    transport = _mock_transport([])

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({"action": "upload_file"})

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


# --- export_file ---


async def test_export_file(tmp_path):
    from fliiq.data.skills.core.google_drive import main as gdrive

    transport = _mock_transport([
        httpx.Response(200, text="col1,col2\nval1,val2", headers={"content-type": "text/csv"}),
    ])

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({
            "action": "export_file",
            "file_id": "sheet1",
            "mime_type": "text/csv",
        })

    assert result["success"] is True
    assert "col1" in result["data"]["content"]


async def test_export_file_missing_params(tmp_path):
    from fliiq.data.skills.core.google_drive import main as gdrive

    transport = _mock_transport([])

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({"action": "export_file", "file_id": "f1"})

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


# --- delete_file ---


async def test_delete_file(tmp_path):
    from fliiq.data.skills.core.google_drive import main as gdrive

    captured_body = None
    captured_method = None

    def capture(request):
        nonlocal captured_body, captured_method
        captured_method = request.method
        if request.content:
            captured_body = json.loads(request.content)
        return httpx.Response(200, json={"id": "f1", "trashed": True})

    transport = httpx.MockTransport(capture)

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({"action": "delete_file", "file_id": "f1"})

    assert result["success"] is True
    assert captured_method == "PATCH"
    assert captured_body["trashed"] is True


# --- Unknown action ---


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

    transport = _mock_transport([])

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({"action": "invalid_action"})

    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_drive import main as gdrive

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

    with _mock_gdrive(tmp_path, transport):
        result = await gdrive.handler({"action": "list_files"})

    assert result["success"] is False
    assert "403" in result["message"]
    assert "Insufficient permissions" in result["message"]
