import time
import subprocess, os, sys
import json, requests
from web import http


class Worker:
    def __init__(self, domain: str, create_admin: bool):
        '''
        @opt:base = base url string
        @opt:create_admin = creates admin account directly off cargo -c <NAME>
        @opt:admin = optionally pass in dictionary with some admin credentials
            potentially from another instance to run multiple tests at once
        '''

        self.domain = domain
        self.requests = {}
        self.responses = {}
        self.body_logs = {}

        self.jwt = None # never gets assigned until /login is hit
        self.basic_creds = self.__create_admin()
        self.id = self.basic_creds['id']
        self.secret = self.basic_creds['secret']

    def __create_admin(self):
        # /home/$user/.cargo/bin/cargo <- normally
        # NOTE: the above path is prolly some awful shit on ci pipeline
        CARGO_BIN = os.getenv('CARGO_BIN')

        proc = subprocess.run(f'cargo run --release -- -c dev-test'.split(), text=True, capture_output=True)
        try: 
            return json.loads(proc.stdout)
        except:
            import sys
            print('TESTBOT UNABLE TO LOAD JSON DATA FROM -c flag', file=sys.stderr)
            exit(1)

    def auth(self, auth_opt: str):
        if auth_opt == 'basic':
            return self.basic_creds
        else:
            return {'id': self.id, 'jwt': auth_opt}
        

    def _append_auth(self, opts: dict, auth: str):
        '''
        Default auth fallback type is jwt because that's they only other type of auth 
        FC cares about really

        @param opts: Dictionary of parameters to pass to the endpoint
        @auth: Denotes if we use basic auth or jwt if its not 'basic'

        '''
        if type(auth) == str:
            opts['id'] = self.id
            if auth == 'basic':
                opts['secret'] = self.secret
            else:
                opts['jwt'] = auth
            return opts
        else:
            # if its not a string we don't add anything in 
            return opts

    def logs(self):
        ids = sorted(self.requests.keys()) # shared keys in requests/responses
        for key in ids:
            self.responses[key].log()

            # Logg the provided data to ensure that _it_ wasn't the cause for error
            resp = self.responses[key]
            if resp.code != resp.expected:
                opts = self.requests[key].params
                print(f'\tParams: {opts}')
            if self.body_logs[key] is True:
                print(f'\tBody: {self.responses[key].body}')


    def request(self, method: str, path: str, auth: str, opts: dict, expectation: int, show_body=False):
        assert(path[0] ==  '/')

        # First make sure we add in the correct auth params that are requested
        opts = self._append_auth(opts, auth)

        # Build the request and store it in our structure
        url = self.domain + path
        req = http.Request(method, url, opts)
        r_id = time.time()

        resp = req.make(expectation)

        # update log trackers
        self.requests[r_id] = req
        self.responses[r_id] = resp
        self.body_logs[r_id] = show_body

        return r_id


def run(worker: Worker):
    VOICE_CHAN = 1
    TEXT_CHAN = 2
    # Basically every test requires a jwt to be passed in so we grab that here
    # Should this fail so should nearly every other test from this point
    req_login = worker.request('post', '/login', 'basic',{}, 200)
    jwt = worker.responses[req_login].json()['jwt']

    new_channel_name = time.time()

    channel_tests = [
        # sanity check
        {'init': ['get', '/channels/list', {}], 'auth': None, 'hope': 401},

        {'init': ['post', '/channels/list', {}], 'auth': jwt, 'hope': 404},

        {'init': ['post', '/channels/create', {'name': str(new_channel_name), 'kind': TEXT_CHAN, 'description': 'asdf'}], 'auth': jwt, 'hope': 200},
        # Just a regular test no saving for this one
        {'init': ['post', '/channels/create', {'name': str(new_channel_name+1), 'kind': TEXT_CHAN,}], 'auth': jwt, 'hope': 200},

        {'init': ['post', '/channels/create', {}], 'auth': jwt, 'hope': 400},
        {'init': ['post', '/channels/create', {'name': 123, 'kind': 'adsf'}], 'auth': jwt, 'hope': 400},

        # save this and compare its results to the previous
        {'init': ['get', '/channels/list', {}], 'auth': jwt, 'hope': 200},
        {'init': ['get', '/channels/list', {'random-param': 123}], 'auth': jwt, 'hope': 200},
    ]

    for test in channel_tests:
        method, path, opts = test['init']
        auth = test['auth']
        hope = test['hope']

        worker.request(method, path, auth, opts, hope)

    msg_chan_name = time.time()
    _id = worker.request('post', '/channels/create', jwt, {
        'name': str(msg_chan_name),
        'kind': TEXT_CHAN,
    }, 200)
    chan_d = worker.responses[_id].json()

    message_tests = [
        # bs message spam
        {'init': ['post', '/message/send', {'channel': chan_d['id'], 'content': 'bs content'}], 'auth': jwt, 'hope': 200},
        {'init': ['post', '/message/send', {'channel': chan_d['id'], 'content': 'bs content'}], 'auth': jwt, 'hope': 200},
        {'init': ['post', '/message/send', {'channel': chan_d['id'], 'content': 'bs content'}], 'auth': jwt, 'hope': 200},
        {'init': ['post', '/message/send', {'channel': chan_d['id'], 'content': 'bs content'}], 'auth': jwt, 'hope': 200},

        # can we get them back tho?
        {
            'init': [
                'get', '/message/get_range', {'channel': chan_d['id'], 'start-time': int(msg_chan_name-10), 'end-time': int(msg_chan_name + 10)}
            ], 
            'auth': jwt, 'hope': 200
        },
        {
            'init': [
                'get', '/message/get_range', {'channel': chan_d['id'], 'end-time': int(msg_chan_name)}
            ], 
            'auth': jwt, 'hope': 400
        },
        {
            'init': [
                'get', '/message/get_range', {'channel': chan_d['id'], 'start-time': int(msg_chan_name), 'end-time': int(msg_chan_name)}
            ], 
            'auth': jwt, 'hope': 400
        },
        {
            'init': [
                'get', '/message/get_range', {'channel': chan_d['id'], 'end-time': int(msg_chan_name), 'start-time': int(msg_chan_name)}
            ], 
            'auth': jwt, 'hope': 400
        },
        # two tests that follow the rules
        {
            'init': [
                'get', '/message/from_id', {'start': 1, 'channel': 3}
            ],
            'auth': jwt, 'hope': 200, 'body': True
        },
        {
            'init': [
                'get', '/message/from_id', {'start':1, 'channel':3, 'limit':2}
            ],
            'auth': jwt, 'hope': 200, 'body': True
        },
        # tests that don't follow the api's rules
        {
            # channel doesn't exist so a 404 seems to be inorder
            'init': [
                'get', '/message/from_id', {'start': 1, 'channel':9}
            ],
            'auth': jwt, 'hope': 404
        },
        {
            'init': [
                # good channel but id is tooo high
                'get', '/message/from_id', {'start': 5, 'channel':3}
            ],
            'auth': jwt, 'hope': 404
        },
    ]

    for test in message_tests:
        method, path, opts = test['init']
        auth = test['auth']
        hope = test['hope']
        if 'body' in test:
            worker.request(method, path, auth, opts, hope, show_body=test['body'])
        else:
            worker.request(method, path, auth, opts, hope)



    worker.logs()

if __name__ == '__main__':
    worker = Worker('http://localhost:8888', create_admin=True)
    run(worker)
