#
# Copyright (C) 2019-2025  Leo P. Singer <leo.singer@ligo.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
"""End-to-end tests against a live server."""

import os
import pkgutil

import pytest
import pytest_asyncio
from httpx import HTTPStatusError, codes

from gracedb_sdk import AsyncClient


def get_coinc_xml_bytes():
    return pkgutil.get_data(__name__, os.path.join("data/coinc.xml"))


@pytest.fixture
def coinc_xml_bytes():
    return get_coinc_xml_bytes()


def authenticated_client_or_skip():
    """Return an authenticated client, or skip the test."""
    try:
        return AsyncClient("https://gracedb-test.ligo.org/api/", fail_if_noauth=True)
    except ValueError:
        pytest.skip("no GraceDB credentials found")


async def skip_if_not_authorized(client):
    """Do test upload to see if we have permissions."""
    try:
        event = await client.events.create(
            filename="coinc.xml",
            filecontents=get_coinc_xml_bytes(),
            group="Test",
            pipeline="gstlal",
        )
        graceid = event["graceid"]
        await client.superevents.create(
            preferred_event=graceid,
            t_start=1000000000.12345678,
            t_0=1000000001.12345678,
            t_end=1000000002.12345678,
        )
    except HTTPStatusError as e:
        if e.response.status_code == codes.FORBIDDEN:
            pytest.skip(
                f"GraceDB credentials not authorized for gracedb-test. HTTP response text: {e.response.text}"
            )
        else:
            raise


@pytest_asyncio.fixture(scope="module", loop_scope="module")
async def client():
    async with authenticated_client_or_skip() as client:
        await skip_if_not_authorized(client)
        yield client


@pytest.mark.asyncio(loop_scope="module")
async def test_user_info(client):
    user_info = await client.user_info.get()
    assert "username" in user_info


@pytest.mark.asyncio(loop_scope="module")
@pytest.mark.parametrize(
    "labels_in,labels_out",
    [
        [{"EM_READY", "DQV"}, ["EM_READY", "DQV"]],
        [["EM_READY", "DQV"], ["EM_READY", "DQV"]],
        [("EM_READY", "DQV"), ["EM_READY", "DQV"]],
        ["EM_READY", ["EM_READY"]],
        [["EM_READY"], ["EM_READY"]],
        [None, []],
        [[], []],
    ],
)
async def test_events_create(client, coinc_xml_bytes, labels_in, labels_out):
    result = await client.events.create(
        filename="coinc.xml",
        filecontents=coinc_xml_bytes,
        group="Test",
        pipeline="gstlal",
        labels=labels_in,
    )
    assert set(result["labels"]) == set(labels_out)


@pytest_asyncio.fixture(loop_scope="module")
async def events_create(client, coinc_xml_bytes):
    return await client.events.create(
        filename="coinc.xml",
        filecontents=coinc_xml_bytes,
        group="Test",
        pipeline="gstlal",
    )


@pytest.mark.asyncio(loop_scope="module")
async def test_events_get(client, events_create):
    event_id = events_create["graceid"]
    result = await client.events[event_id].get()
    assert events_create["graceid"] == result["graceid"]


@pytest.mark.asyncio(loop_scope="module")
async def test_events_update(client, events_create, coinc_xml_bytes):
    event_id = events_create["graceid"]
    await client.events.update(
        event_id, filename="coinc.xml", filecontents=coinc_xml_bytes + b"<!--foobar-->"
    )


@pytest.mark.asyncio(loop_scope="module")
async def test_events_search(client, events_create):
    event_id = events_create["graceid"]
    result = [item async for item in client.events.search(query=event_id)]
    assert len(result) == 1
    assert events_create["graceid"] == result[0]["graceid"]


@pytest_asyncio.fixture(loop_scope="module")
async def events_labels_create(client, events_create):
    event_id = events_create["graceid"]
    return await client.events[event_id].labels.create("SKYMAP_READY")


@pytest.mark.asyncio(loop_scope="module")
async def test_events_events_labels_create(client, events_create, events_labels_create):
    event_id = events_create["graceid"]
    result = await client.events[event_id].labels.get()
    assert any(item["name"] == "SKYMAP_READY" for item in result)


@pytest.mark.asyncio(loop_scope="module")
async def test_events_labels_delete(client, events_create, events_labels_create):
    event_id = events_create["graceid"]
    await client.events[event_id].labels.delete("SKYMAP_READY")
    result = await client.events[event_id].labels.get()
    assert not any(item["name"] == "SKYMAP_READY" for item in result)


@pytest.mark.asyncio(loop_scope="module")
@pytest.mark.parametrize("filename,filecontents", [[None, None], ["foo.txt", "bar"]])
@pytest.mark.parametrize(
    "tags_in,tags_out",
    [
        [["emfollow", "p_astro"], ["emfollow", "p_astro"]],
        ["emfollow", ["emfollow"]],
        [None, []],
    ],
)
@pytest.mark.parametrize("label", [None, "HIGH_PROFILE"])
async def test_events_logs_create(
    client, events_create, filename, filecontents, tags_in, tags_out, label
):
    event_id = events_create["graceid"]
    result = await client.events[event_id].logs.create(
        comment="plugh",
        filename=filename,
        filecontents=filecontents,
        tags=tags_in,
        label=label,
    )
    if filename is None:
        assert result["filename"] == ""
    else:
        assert result["filename"] == filename
    assert set(result["tag_names"]) == set(tags_out)
    result = await client.events[event_id].labels.get()
    if label is not None:
        assert any(item["name"] == label for item in result)


@pytest.mark.asyncio(loop_scope="module")
async def test_events_logs_get(client, events_create):
    event_id = events_create["graceid"]
    result = await client.events[event_id].logs.get()
    assert result[0]["filename"] == "coinc.xml"


@pytest.mark.asyncio(loop_scope="module")
async def test_events_files_get(client, events_create):
    event_id = events_create["graceid"]
    result = await client.events[event_id].files.get()
    assert "coinc.xml" in result


@pytest.mark.asyncio(loop_scope="module")
async def test_events_files_get_alternate(client, events_create):
    event_id = events_create["graceid"]
    result = await client.superevents[event_id].files.get()
    assert "coinc.xml" in result


@pytest.mark.asyncio(loop_scope="module")
async def test_events_files_file_get(client, events_create):
    event_id = events_create["graceid"]
    await client.events[event_id].logs.create(
        comment="plugh", filename="foo.txt", filecontents=b"bar"
    )
    assert (await client.events[event_id].files["foo.txt"].get()).content == b"bar"


@pytest_asyncio.fixture(loop_scope="module")
async def events_logs_tags_create(client, events_create):
    event_id = events_create["graceid"]
    await client.events[event_id].logs[1].tags.create("em_bright")


@pytest.mark.asyncio(loop_scope="module")
async def test_events_logs_tags_create(client, events_create, events_logs_tags_create):
    event_id = events_create["graceid"]
    result = await client.events[event_id].logs[1].tags.get()
    assert result[0]["name"] == "em_bright"


@pytest.mark.asyncio(loop_scope="module")
async def test_events_logs_tags_delete(client, events_create, events_logs_tags_create):
    event_id = events_create["graceid"]
    await client.events[event_id].logs[1].tags.delete("em_bright")
    result = await client.events[event_id].logs[1].tags.get()
    assert len(result) == 0


@pytest_asyncio.fixture(loop_scope="module")
async def events_voevents_create(client, events_create):
    event_id = events_create["graceid"]
    return await client.events[event_id].voevents.create(voevent_type="earlywarning")


@pytest.mark.asyncio(loop_scope="module")
async def test_events_voevents_create(client, events_create, events_voevents_create):
    event_id = events_create["graceid"]
    filename = "{}-1-EarlyWarning.xml".format(event_id)
    assert events_voevents_create["filename"] == filename


@pytest.mark.asyncio(loop_scope="module")
async def test_events_voevents_get(client, events_create, events_voevents_create):
    event_id = events_create["graceid"]
    result = await client.events[event_id].voevents.get()
    assert len(result) == 1
    filename = "{}-1-EarlyWarning.xml".format(event_id)
    assert result[0]["filename"] == filename


@pytest_asyncio.fixture(loop_scope="module")
async def superevents_create(client, events_create):
    event_id = events_create["graceid"]
    return await client.superevents.create(
        preferred_event=event_id,
        t_start=1000000000.12345678,
        t_0=1000000001.12345678,
        t_end=1000000002.12345678,
    )


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_create(client, events_create, superevents_create):
    event_id = events_create["graceid"]
    assert superevents_create["preferred_event"] == event_id
    assert superevents_create["t_start"] == 1000000000.123457
    assert superevents_create["t_0"] == 1000000001.123457
    assert superevents_create["t_end"] == 1000000002.123457


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_get(client, superevents_create):
    superevent_id = superevents_create["superevent_id"]
    result = await client.superevents[superevent_id].get()
    assert superevents_create == result


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_search(client, superevents_create):
    superevent_id = superevents_create["superevent_id"]
    query = "{} category: Test".format(superevent_id)
    result = [item async for item in client.superevents.search(query=query)]
    assert [superevents_create] == result


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_update(client, superevents_create):
    superevent_id = superevents_create["superevent_id"]
    await client.superevents.update(superevent_id, t_start=123, t_0=456, t_end=789)
    result = await client.superevents[superevent_id].get()
    assert result["t_start"] == 123
    assert result["t_0"] == 456
    assert result["t_end"] == 789


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_update_preferred_event_none(client, superevents_create):
    # Regression test for https://git.ligo.org/emfollow/gracedb-sdk/issues/13
    superevent_id = superevents_create["superevent_id"]
    await client.superevents.update(
        superevent_id, preferred_event=None, t_start=123, t_0=456, t_end=789
    )
    result = await client.superevents[superevent_id].get()
    assert result["t_start"] == 123
    assert result["t_0"] == 456
    assert result["t_end"] == 789


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_update_preferred_event_same(
    client, events_create, superevents_create
):
    # Check that passing current preferred event is OK
    superevent_id = superevents_create["superevent_id"]
    event_id = events_create["graceid"]
    await client.superevents.update(
        superevent_id, preferred_event=event_id, t_start=123, t_0=456, t_end=789
    )
    result = await client.superevents[superevent_id].get()
    assert result["t_start"] == 123
    assert result["t_0"] == 456
    assert result["t_end"] == 789


@pytest_asyncio.fixture(loop_scope="module")
async def events_create_2(client, coinc_xml_bytes):
    return await client.events.create(
        filename="coinc.xml",
        filecontents=coinc_xml_bytes,
        group="Test",
        pipeline="gstlal",
    )


@pytest_asyncio.fixture(loop_scope="module")
async def superevents_add(client, superevents_create, events_create_2):
    event_id_2 = events_create_2["graceid"]
    superevent_id = superevents_create["superevent_id"]
    await client.superevents[superevent_id].add(event_id_2)


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_add(
    client, superevents_add, superevents_create, events_create, events_create_2
):
    event_id = events_create["graceid"]
    event_id_2 = events_create_2["graceid"]
    superevent_id = superevents_create["superevent_id"]
    result = await client.superevents[superevent_id].get()
    assert set(result["gw_events"]) == {event_id, event_id_2}
    assert result["preferred_event"] == event_id


@pytest_asyncio.fixture(loop_scope="module")
async def superevents_update_change_preferred_event(
    client, superevents_create, events_create_2
):
    event_id_2 = events_create_2["graceid"]
    superevent_id = superevents_create["superevent_id"]
    await client.superevents.update(superevent_id, preferred_event=event_id_2)


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_update_change_preferred_event(
    client,
    superevents_add,
    superevents_create,
    superevents_update_change_preferred_event,
    events_create,
    events_create_2,
):
    event_id = events_create["graceid"]
    event_id_2 = events_create_2["graceid"]
    superevent_id = superevents_create["superevent_id"]
    result = await client.superevents[superevent_id].get()
    assert set(result["gw_events"]) == {event_id, event_id_2}
    assert result["preferred_event"] == event_id_2


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_remove(
    client, superevents_add, superevents_create, events_create, events_create_2
):
    event_id = events_create["graceid"]
    event_id_2 = events_create_2["graceid"]
    superevent_id = superevents_create["superevent_id"]
    await client.superevents[superevent_id].remove(event_id_2)
    result = await client.superevents[superevent_id].get()
    assert set(result["gw_events"]) == {event_id}


@pytest_asyncio.fixture(loop_scope="module")
async def superevents_labels_create(client, superevents_create):
    superevent_id = superevents_create["superevent_id"]
    return await client.superevents[superevent_id].labels.create("SKYMAP_READY")


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_superevents_labels_create(
    client, superevents_create, superevents_labels_create
):
    superevent_id = superevents_create["superevent_id"]
    result = await client.superevents[superevent_id].labels.get()
    assert any(item["name"] == "SKYMAP_READY" for item in result)


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_labels_delete(
    client, superevents_create, superevents_labels_create
):
    superevent_id = superevents_create["superevent_id"]
    await client.superevents[superevent_id].labels.delete("SKYMAP_READY")
    result = await client.superevents[superevent_id].labels.get()
    assert not any(item["name"] == "SKYMAP_READY" for item in result)


@pytest.mark.asyncio(loop_scope="module")
@pytest.mark.parametrize("filename,filecontents", [[None, None], ["foo.txt", "bar"]])
@pytest.mark.parametrize(
    "tags_in,tags_out",
    [
        [["emfollow", "p_astro"], ["emfollow", "p_astro"]],
        ["emfollow", ["emfollow"]],
        [None, []],
    ],
)
@pytest.mark.parametrize("label", [None, "HIGH_PROFILE"])
async def test_superevents_logs_create(
    client, superevents_create, filename, filecontents, tags_in, tags_out, label
):
    superevent_id = superevents_create["superevent_id"]
    result = await client.superevents[superevent_id].logs.create(
        comment="plugh",
        filename=filename,
        filecontents=filecontents,
        tags=tags_in,
        label=label,
    )
    if filename is None:
        assert result["filename"] == ""
    else:
        assert result["filename"] == filename
    assert set(result["tag_names"]) == set(tags_out)
    result = await client.events[superevent_id].labels.get()
    if label is not None:
        assert any(item["name"] == label for item in result)


@pytest_asyncio.fixture(loop_scope="module")
async def superevents_logs_create(client, superevents_create):
    superevent_id = superevents_create["superevent_id"]
    return await client.superevents[superevent_id].logs.create(
        comment="plugh", filename="foo.txt", filecontents="bar"
    )


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_logs_get(
    client, superevents_create, superevents_logs_create
):
    superevent_id = superevents_create["superevent_id"]
    result = await client.superevents[superevent_id].logs.get()
    assert result[-1]["filename"] == "foo.txt"


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_files_get(
    client, superevents_create, superevents_logs_create
):
    superevent_id = superevents_create["superevent_id"]
    result = await client.superevents[superevent_id].files.get()
    assert "foo.txt" in result


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_files_get_alternate(
    client, superevents_create, superevents_logs_create
):
    superevent_id = superevents_create["superevent_id"]
    result = await client.events[superevent_id].files.get()
    assert "foo.txt" in result


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_files_file_get(
    client, superevents_create, superevents_logs_create
):
    superevent_id = superevents_create["superevent_id"]
    assert (
        await client.superevents[superevent_id].files["foo.txt"].get()
    ).content == b"bar"


@pytest_asyncio.fixture(loop_scope="module")
async def superevents_logs_tags_create(
    client, superevents_create, superevents_logs_create
):
    superevent_id = superevents_create["superevent_id"]
    await client.superevents[superevent_id].logs[1].tags.create("em_bright")


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_logs_tags_create(
    client, superevents_create, superevents_logs_tags_create
):
    superevent_id = superevents_create["superevent_id"]
    result = await client.superevents[superevent_id].logs[1].tags.get()
    assert result[0]["name"] == "em_bright"


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_logs_tags_delete(
    client, superevents_create, superevents_logs_tags_create
):
    superevent_id = superevents_create["superevent_id"]
    await client.superevents[superevent_id].logs[1].tags.delete("em_bright")
    result = await client.superevents[superevent_id].logs[1].tags.get()
    assert len(result) == 0


@pytest_asyncio.fixture(loop_scope="module")
async def superevents_voevents_create(client, superevents_create):
    superevent_id = superevents_create["superevent_id"]
    return await client.superevents[superevent_id].voevents.create(
        voevent_type="earlywarning"
    )


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_voevents_create(
    client, superevents_create, superevents_voevents_create
):
    superevent_id = superevents_create["superevent_id"]
    filename = "{}-1-EarlyWarning.xml".format(superevent_id)
    assert superevents_voevents_create["filename"] == filename


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_voevents_get(
    client, superevents_create, superevents_voevents_create
):
    superevent_id = superevents_create["superevent_id"]
    result = await client.superevents[superevent_id].voevents.get()
    filename = "{}-1-EarlyWarning.xml".format(superevent_id)
    assert len(result) == 1
    assert result[0]["filename"] == filename


@pytest.mark.asyncio(loop_scope="module")
@pytest.mark.skip("CI/CD robot keytab is not authorized")
async def test_superevents_expose(client, superevents_create):
    superevent_id = superevents_create["superevent_id"]
    await client.superevents[superevent_id].expose()
    assert await client.superevents[superevent_id].is_exposed()
    await client.superevents[superevent_id].unexpose()
    assert not await client.superevents[superevent_id].is_exposed()


@pytest.mark.asyncio(loop_scope="module")
@pytest.mark.parametrize("status", ["OK", "NO"])
@pytest.mark.skip("CI/CD robot keytab is not authorized")
async def test_superevents_signoff_operator(client, superevents_create, status):
    superevent_id = superevents_create["superevent_id"]

    # We generally don't have permission to do operator signoffs.
    with pytest.raises(HTTPStatusError) as exc_info:
        await client.superevents[superevent_id].signoff("H1", status, comment="foo")
    assert exc_info.value.response.status_code == 403


@pytest.mark.asyncio(loop_scope="module")
@pytest.mark.parametrize("status", ["OK", "NO"])
@pytest.mark.skip("CI/CD robot keytab is not authorized")
async def test_superevents_signoff_advocate(client, superevents_create, status):
    superevent_id = superevents_create["superevent_id"]
    await client.superevents[superevent_id].labels.create("ADVREQ")
    await client.superevents[superevent_id].signoff("ADV", status, comment="foo")
    result = await client.superevents[superevent_id].labels.get()
    labels = {row["name"] for row in result}
    assert "ADV" + status in labels


@pytest.mark.asyncio(loop_scope="module")
async def test_superevents_pipeline_preferred_events(
    client, events_create, events_create_2, superevents_create, superevents_add
):
    event_id = events_create["graceid"]
    event_id_2 = events_create_2["graceid"]
    superevent_id = superevents_create["superevent_id"]
    prefs = client.superevents[superevent_id].pipeline_preferred_events

    assert await prefs.get() == []

    await prefs.add(event_id)
    assert await prefs.get() == [event_id]

    with pytest.raises(HTTPStatusError) as exc_info:
        await prefs.add(event_id)
    assert exc_info.value.response.status_code == codes.BAD_REQUEST
    assert (
        exc_info.value.response.text
        == f'"Event {event_id} is already the gstlal pipeline-preferred event for {superevent_id}"'
    )

    await prefs.add(event_id_2)
    assert await prefs.get() == [event_id_2]

    await prefs.delete(event_id_2)
    assert await prefs.get() == []

    with pytest.raises(HTTPStatusError) as exc_info:
        await prefs.delete(event_id)
    assert exc_info.value.response.status_code == codes.NOT_FOUND
