#!/usr/bin/python3

#
# Runtime Tests for Host Services
#

import errno
import os
import sys
import tempfile
from typing import Any

import pytest

from osbuild import host
from osbuild.util.jsoncomm import FdSet


class ServiceTest(host.Service):

    def __init__(self, args):
        super().__init__(args)
        self.fds = []

    def register_fds(self, fds):
        self.fds.extend(fds)

    def dispatch(self, method: str, args: Any, fds: FdSet):
        ret = None

        if method == "exception":
            raise ValueError("Remote Exception")
        if method == "echo":
            ret = args
        elif method == "echo-fd":
            ret = args
            with tempfile.TemporaryFile("w+") as f:
                with os.fdopen(fds.steal(0)) as d:
                    f.write(d.read())
                f.seek(0)
                fds = [os.dup(f.fileno())]
                self.register_fds(fds)

        elif method == "identify":
            ret = self.id
        elif method == "invalid-fd":
            ret = []
            with tempfile.TemporaryFile("w+") as f:
                valid_fd = os.dup(f.fileno())
            invalid_fd = valid_fd + 10
            fds = [valid_fd, invalid_fd]
            self.register_fds([valid_fd])
        elif method == "check-fds-are-closed":
            while self.fds:
                fd = self.fds.pop()
                try:
                    os.close(fd)
                except OSError as e:
                    if e.errno == errno.EBADF:
                        print(f"fd '{fd}' was closed")
                        continue
                    raise
                raise ValueError(f"fd '{fd}' was not closed")
        elif method == "signal_me_3_times":
            self.emit_signal(0)
            self.emit_signal(1)
            self.emit_signal(2)
        elif method == "signal_me_on_fd":
            with tempfile.TemporaryFile("w+") as f:
                with os.fdopen(fds.steal(0)) as d:
                    f.write(d.read())
                f.seek(0)
                fds = [os.dup(f.fileno())]
                self.register_fds(fds)
                self.emit_signal("that should do it", fds)
        else:
            raise host.ProtocolError("unknown method:", method)

        return ret, fds


def test_basic():
    with host.ServiceManager() as mgr:
        for i in range(3):
            client = mgr.start(str(i), __file__)

            args = ["an", "argument"]
            res = client.call("echo", args)

            assert args == res

            remote_id = client.call("identify")
            assert remote_id == str(i)

            with pytest.raises(ValueError, match=f"{str(i)}"):
                _ = mgr.start(str(i), __file__)

        for i in range(3):
            client = mgr.services[str(i)]
            client.stop()


def test_pass_fd():
    with host.ServiceManager() as mgr:
        for i in range(3):
            client = mgr.start(str(i), __file__)

            args = ["an", "argument"]
            data = "osbuild\n"

            with tempfile.TemporaryFile("w+") as f:
                f.write(data)
                f.seek(0)

                res, fds = client.call_with_fds("echo-fd", args, fds=[f.fileno()])

            assert args == res
            with os.fdopen(fds.steal(0)) as d:
                assert data == d.read()

            client.call_with_fds("check-fds-are-closed")

            remote_id = client.call("identify")
            assert remote_id == str(i)

            with pytest.raises(ValueError, match=f"{str(i)}"):
                _ = mgr.start(str(i), __file__)

        for i in range(3):
            client = mgr.services[str(i)]
            client.stop()


def test_pass_fd_invalid():
    with host.ServiceManager() as mgr:

        client = mgr.start(str("test-invalid-fd"), __file__)
        with pytest.raises(host.RemoteError):
            client.call_with_fds("invalid-fd")
        client.call_with_fds("check-fds-are-closed")


def test_exception():
    with host.ServiceManager() as mgr:
        client = mgr.start("exception", __file__)
        with pytest.raises(host.RemoteError, match=r"Remote Exception"):
            client.call("exception")


def test_signals():
    with host.ServiceManager() as mgr:
        exec_callback = 0

        def check_value(item, _fds):
            nonlocal exec_callback
            assert item == exec_callback
            exec_callback += 1
        client = mgr.start("test_signal_me_3_times", __file__)
        client.call_with_fds("signal_me_3_times", on_signal=check_value)
        assert exec_callback == 3


def test_signals_on_separate_fd():
    with host.ServiceManager() as mgr:

        data = "osbuild\n"
        exec_callback = False

        def check_value(item, fds):
            nonlocal exec_callback
            exec_callback = True
            assert item == "that should do it"
            with os.fdopen(fds.steal(0)) as d:
                assert data == d.read()

        client = mgr.start("test_signal_me_on_fd", __file__)

        with tempfile.TemporaryFile("w+") as f:
            f.write(data)
            f.seek(0)

            client.call_with_fds("signal_me_on_fd", fds=[f.fileno()], on_signal=check_value)
        assert exec_callback


def main():
    service = ServiceTest.from_args(sys.argv[1:])
    service.main()


if __name__ == "__main__":
    main()
