"""
Unit test for presee-model.
The preseem-module dynamically loads model protobufs, so to get code coverage
there is no need to test every one - plus we don't want to have to update this
test case when the model protobufs are changed but the preseem_model code is
not changed.  Therefore we test one part of the model (Company) for coverage.

This uses the grpc_testing module (pip install grpcio-testing).
I used the following examples to figure out how to use it:
https://github.com/grpc/grpc/blob/master/src/python/grpcio_tests/tests/testing/_client_test.py
"""
import asyncio
from concurrent.futures import ThreadPoolExecutor
import unittest
from uuid import uuid4

import google.protobuf.timestamp_pb2
import grpc
import grpc_testing

from preseem import PreseemGrpcClient, PreseemModel
from preseem_protobuf.model import company_pb2, company_pb2_grpc

class StubGrpcClient(PreseemGrpcClient):
    def __init__(self):
        super().__init__(api_key=None)

    async def connect(self):
        self._channel = grpc_testing.channel(company_pb2.DESCRIPTOR.services_by_name.values(), grpc_testing.strict_real_time())


class TestPreseemModel(unittest.TestCase):
    def setUp(self):
        self.thread_pool = ThreadPoolExecutor(1)
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        self.client = StubGrpcClient()
        self.model = PreseemModel(self.client, cache=True)
        self.loop.run_until_complete(self.model.init())

    def tearDown(self):
        self.loop.close()
        self.thread_pool.shutdown(wait=True)

    def _delete(self, cfg):
        return self.loop.run_until_complete(self.model.delete(cfg))

    def _list(self):
        return self.loop.run_until_complete(self.model.list())

    def _listen(self, cbk=None):
        return self.loop.run_until_complete(self.model.listen(cbk))

    def _await(self, co):
        return self.loop.run_until_complete(co)

    async def _do_rpc(self, opname, op, response):
        """Execute an RPC using the grpc-testing unit testing framework."""
        task_op = asyncio.create_task(op)
        await asyncio.sleep(0)  # yield the eventloop
        md, req, rpc = self.client._channel.take_unary_stream(company_pb2.DESCRIPTOR.services_by_name['CompanyService'].methods_by_name[opname])
        rpc.send_initial_metadata(())
        if isinstance(response, list):
            for x in response:
                rpc.send_response(x)
        else:
            rpc.send_response(response)
        rpc.terminate((), grpc.StatusCode.OK, '')
        return await asyncio.wait_for(task_op, timeout=1)

    def _test_op(self, opname, op, response):
        return self.loop.run_until_complete(self._do_rpc(opname, op, response))

    def test_test_1(self):
        """Test the List operation."""
        t = 1618245749.950572
        ts = google.protobuf.timestamp_pb2.Timestamp()
        ts.FromNanoseconds(int(t * 1000000000))
        companies = [
            company_pb2.Company(id=1, uuid=uuid4().bytes, name='company1', inactive=False, created_at=ts),
            company_pb2.Company(id=2, uuid=uuid4().bytes, name='company2', inactive=True, created_at=ts)
        ]
        r = self._test_op('List', self.model.Company.List(), companies)
        self.assertIsInstance(r, list)
        self.assertEqual(len(r), 2)
        for i in range(2):
            self.assertEqual(r[i].id, companies[i].id)
            self.assertEqual(r[i].uuid, companies[i].uuid)
            self.assertEqual(r[i].name, companies[i].name)
            self.assertEqual(r[i].inactive, companies[i].inactive)
            self.assertEqual(r[i].created_at, t)
