import synapse.exc as s_exc
import synapse.common as s_common
import synapse.cortex as s_cortex

import synapse.lib.nexus as s_nexus
import synapse.tests.utils as s_t_utils

class SampleNexus(s_nexus.Pusher):

    async def __anit__(self, iden, nexsroot=None):
        await s_nexus.Pusher.__anit__(self, iden=iden, nexsroot=nexsroot)
        self.iden = iden

    async def doathing(self, eventdict):
        return await self._push('thing:doathing', eventdict, 'foo')

    @s_nexus.Pusher.onPush('thing:doathing')
    async def _doathinghandler(self, eventdict, anotherparm):
        eventdict['happened'] = self.iden
        return anotherparm

    async def _push(self, event, *args, **kwargs):
        eventdict = args[0]
        eventdict['specialpush'] += 1
        return await s_nexus.Pusher._push(self, event, *args, **kwargs)

    async def doathing2(self, eventdict):
        return await self._push('thing:doathing2', eventdict, 'foo')

    @s_nexus.Pusher.onPush('thing:doathing2', passitem=True)
    async def _doathing2handler(self, eventdict, anotherparm, nexsitem=None):
        nexsoff, nexsmesg = nexsitem
        eventdict['gotindex'] = nexsoff
        return anotherparm

    @s_nexus.Pusher.onPushAuto('auto2')
    async def doathingauto(self, eventdict, anotherparm):
        eventdict['autohappened'] = self.iden
        return anotherparm

    @s_nexus.Pusher.onPushAuto('auto1')
    async def doathingauto2(self, eventdict, anotherparm):
        '''doc'''
        eventdict['autohappened2'] = self.iden
        return anotherparm

    @s_nexus.Pusher.onPushAuto('auto3')
    async def doathingauto3(self, eventdict):
        raise s_exc.SynErr(mesg='Test error')

class SampleMixin(metaclass=s_nexus.RegMethType):
    @s_nexus.Pusher.onPushAuto('mixinthing')
    async def mixinthing(self, eventdict):
        eventdict['autohappened3'] = self.iden
        return 42

class SampleNexus2(SampleNexus, SampleMixin):
    async def doathing(self, eventdict):
        return await self._push('thing:doathing', eventdict, 'bar')

class NexusTest(s_t_utils.SynTest):

    async def test_nexus(self):

        with self.getTestDir() as dirn:

            async with await s_nexus.NexsRoot.anit(dirn) as nexsroot:
                await nexsroot.startup(None)

                async with await SampleNexus.anit(1, nexsroot=nexsroot) as nexus1:

                    eventdict = {'specialpush': 0}
                    self.eq('foo', await nexus1.doathing(eventdict))
                    self.eq(1, eventdict.get('happened'))

                    self.eq('foo', await nexus1.doathingauto(eventdict, 'foo'))
                    self.eq(1, eventdict.get('autohappened'))

                    self.eq('foo', await nexus1.doathingauto2(eventdict, 'foo'))
                    self.eq(1, eventdict.get('autohappened2'))

                    self.eq('doc', nexus1.doathingauto2.__doc__)

                    self.eq(3, await nexsroot.index())

                    async with await SampleNexus2.anit(2, nexsroot=nexsroot) as testkid:

                        eventdict = {'specialpush': 0}
                        # Tricky inheriting handler funcs themselves
                        self.eq('foo', await nexus1.doathing(eventdict))
                        self.eq('bar', await testkid.doathing(eventdict))
                        self.eq(2, eventdict.get('happened'))

                        # Check offset passing
                        self.eq('foo', await testkid.doathing2(eventdict))
                        self.eq(5, eventdict.get('gotindex'))

                        # Check raising an exception
                        await self.asyncraises(s_exc.SynErr, testkid.doathingauto3(eventdict))

                        with self.getLoggerStream('synapse.lib.nexus') as stream:
                            await nexsroot.recover()

                        stream.seek(0)
                        self.isin('while replaying log', stream.read())

    async def test_nexus_mixin(self):
        with self.getTestDir() as dirn:

            async with await s_nexus.NexsRoot.anit(dirn) as nexsroot:
                await nexsroot.startup(None)

                eventdict = {'specialpush': 0}
                async with await SampleNexus2.anit(2, nexsroot=nexsroot) as testkid:
                    self.eq('bar', await testkid.doathing(eventdict))
                    self.eq(42, await testkid.mixinthing(eventdict))

    async def test_nexus_no_logging(self):
        '''
        Pushers/NexsRoot works with donexslog=False
        '''
        with self.getTestDir() as dirn:

            async with await s_nexus.NexsRoot.anit(dirn, donexslog=False) as nexsroot:

                async with await SampleNexus.anit(1, nexsroot=nexsroot) as nexus1:

                    await nexsroot.startup(None)

                    eventdict = {'specialpush': 0}
                    self.eq('foo', await nexus1.doathing(eventdict))
                    self.eq('foo', await nexus1.doathing2(eventdict))
                    self.eq(1, eventdict.get('happened'))
                    self.eq(1, eventdict.get('gotindex'))

                    self.eq(2, await nexsroot.index())

                    async with await SampleNexus2.anit(2, nexsroot=nexsroot) as nexus2:
                        eventdict = {'specialpush': 0}
                        self.eq('bar', await nexus2.doathing(eventdict))
                        self.eq('foo', await nexus2.doathing2(eventdict))
                        self.eq(2, eventdict.get('happened'))
                        self.eq(3, eventdict.get('gotindex'))

    async def test_nexus_migration(self):
        with self.getRegrDir('cortexes', 'reindex-byarray3') as regrdirn:
            slabsize00 = s_common.getDirSize(regrdirn)
            async with await s_cortex.Cortex.anit(regrdirn) as core00:
                slabsize01 = s_common.getDirSize(regrdirn)
                # Ensure that realsize hasn't grown wildly. That would be indicative
                # of a sparse file copy and not a directory move.
                self.lt(slabsize01[0], 3 * slabsize00[0])

                nexsindx = await core00.getNexsIndx()
                layrindx = max([await layr.getEditIndx() for layr in core00.layers.values()])
                self.gt(nexsindx, layrindx)

                retn = await core00.nexsroot.nexslog.get(0)
                self.nn(retn)
                self.eq([0], core00.nexsroot.nexslog._ranges)
                items = await s_t_utils.alist(core00.nexsroot.nexslog.iter(0))
                self.ge(len(items), 62)

    async def test_nexus_setindex(self):

        async with self.getRegrCore('migrated-nexuslog') as core00:

            nexsindx = await core00.getNexsIndx()
            layrindx = max([await layr.getEditIndx() for layr in core00.layers.values()])
            self.ge(nexsindx, layrindx)

            # Make sure a mirror gets updated to the correct index
            url = core00.getLocalUrl()
            core01conf = {'mirror': url}

            async with self.getRegrCore('migrated-nexuslog', conf=core01conf) as core01:

                await core01.sync()

                layrindx = max([await layr.getEditIndx() for layr in core01.layers.values()])
                self.ge(nexsindx, layrindx)

            # Can only move index forward
            self.false(await core00.setNexsIndx(0))

        # Test with nexuslog disabled
        nologconf = {'nexslog:en': False}
        async with self.getRegrCore('migrated-nexuslog', conf=nologconf) as core:

            nexsindx = await core.getNexsIndx()
            layrindx = max([await layr.getEditIndx() for layr in core.layers.values()])
            self.ge(nexsindx, layrindx)
