
from datetime import datetime

from scipy.integrate._ivp.dop853_coefficients import B
import socket
import hmac
import ssl
import uuid
import time

class ftx_fix:
    def __init__(self, api_key, api_secret):
        self.seq_num = 1
        self.api_key = api_key
        self.api_secret = api_secret
        self.host = 'fix.ftx.com'
        self.port = 4363
        self.context = ssl.create_default_context()
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.ssl_sock = self.context.wrap_socket(self.s, server_hostname=self.host)
        self.ssl_sock.connect((self.host, self.port))
        self._last_recv_time = time.time()
        self._heartbeat_interval = 24
        self.messages = []


    def check_sum(self,s):
        sum = 0
        for char in s:
            sum += ord(char)
        sum = str(sum % 256)
        while len(sum) < 3:
            sum = '0' + sum
        return sum

    def sign(self,t, msg_type, seq_num, api_key, secret):
        message   = "\x01".join([t, msg_type, seq_num, api_key, "FTX"]).encode("utf-8")
        signed = hmac.new(secret.encode(), message, 'sha256').hexdigest()
        return signed

    def wrap_fix_string(self,msg_type, body):
        bodyLength = len("35={}|".format(msg_type)) + len(body)
        header     = "8=FIX.4.2|9={}|35={}|".format(bodyLength, msg_type)
        msg        = header + body
        return msg

    def generate_login_string(self,seq_num, t, api_key, secret):
        msgType = 'A'
        t = datetime.utcnow().strftime('%Y%m%d-%H:%M:%S')
        sign_b64 = self.sign(t, msgType, seq_num, api_key, secret)
        body = f"49={api_key}|56=FTX|34={seq_num}|52={t}|95={len(sign_b64)}|96={sign_b64}|98=0|108=30|" # using the same time (t) and seq_num as in signed text
        msg = self.wrap_fix_string(msgType, body)
        msg = msg.replace('|', '\x01')
        c_sum = self.check_sum(msg)
        msg =  msg + "10={}\x01".format(c_sum)
        
        return msg.encode('ascii')
        
    def generate_logout_string(self,seq_num, t, api_key):
        msgType = "5"
        body = f"49={api_key}|56=FTX|34={seq_num}|52={t}|" # using the same time (t) and seq_num as in signed text
        msg = self.wrap_fix_string(msgType, body)
        msg = msg.replace('|', '\x01')
        c_sum = self.check_sum(msg)
        msg = msg + "10={}\x01".format(c_sum)    
        return msg.encode('ascii')



    def generate_order_string(self,seq_num, t, api_key,oid, symbol,odtyp,quantity,price,side):
        msgType = "D"
        handllnst = '1'
        timeinforce = '1'
        body = f"49={api_key}|56=FTX|34={seq_num}|52={t}|21={handllnst}|11={oid}|55={symbol}|40={odtyp}|38={quantity}|44={price}|54={side}|59={timeinforce}|" # using the same time (t) and seq_num as in signed text
        msg = self.wrap_fix_string(msgType, body)
        msg = msg.replace('|', '\x01')
        c_sum = self.check_sum(msg)
        msg = msg + "10={}\x01".format(c_sum)    
        return msg.encode('ascii')


    def generate_query_order_string(self,seq_num,t,api_key,cl_oid):
        msgType = "H"
        fill_info = 'Y'
        body = f"49={api_key}|56=FTX|34={seq_num}|52={t}|41={cl_oid}|20000={fill_info}|" # using the same time (t) and seq_num as in signed text
        msg = self.wrap_fix_string(msgType, body)
        msg = msg.replace('|', '\x01')
        c_sum = self.check_sum(msg)
        msg = msg + "10={}\x01".format(c_sum)    
        return msg.encode('ascii')


    def generate_cancel_order_string(self,seq_num,t,api_key,cl_oid):
        msgType = "F"
        body = f"49={api_key}|56=FTX|34={seq_num}|52={t}|41={cl_oid}|" # using the same time (t) and seq_num as in signed text
        msg = self.wrap_fix_string(msgType, body)
        msg = msg.replace('|', '\x01')
        c_sum = self.check_sum(msg)
        msg = msg + "10={}\x01".format(c_sum)    
        return msg.encode('ascii')



    def generate_heartbeat_string(self,seq_num,t,api_key,treqid):
        msgType = '0'
        body = ""
        if treqid == "":
            body = f"49={api_key}|56=FTX|34={seq_num}|52={t}|" # using the same time (t) and seq_num as in signed texts
        else:
            body = f"49={api_key}|56=FTX|34={seq_num}|52={t}|112={treqid}|" # using the same time (t) and seq_num as in signed text
        msg = self.wrap_fix_string(msgType, body)
        msg = msg.replace('|', '\x01')
        c_sum = self.check_sum(msg)
        msg = msg + "10={}\x01".format(c_sum)    
        return msg.encode('ascii')


            
    def send_login(self):
        t = str(datetime.utcnow()).replace("-","").replace(" ", "-")[:-7]
        message =  self.generate_login_string(str(self.seq_num), t, self.api_key, self.api_secret)
        self.ssl_sock.sendall(message)
        print(message.replace(b'\x01', b'|'))
        self.seq_num +=1
        self.handle_login_message()

    def send_heartbeat(self,treqid=""):
        t = str(datetime.utcnow()).replace("-","").replace(" ", "-")[:-7]
        message = self.generate_heartbeat_string(str(self.seq_num), t, self.api_key,treqid=treqid)
        self.ssl_sock.sendall(message)
        #print(message.replace(b'\x01', b'|'))
        self.seq_num +=1


    def send_limit_order(self,symbol,order_qty,limit_price,side):
        start_time = datetime.now()
        print("sending limit order")
        if side == "buy":
            side = "1"
        elif side == "sell":
            side = "2"
        cl_oid = uuid.uuid1()
        t = str(datetime.utcnow()).replace("-","").replace(" ", "-")[:-7]
        message =  self.generate_order_string(str(self.seq_num), t, self.api_key, oid=cl_oid, symbol=symbol,odtyp=2,quantity=order_qty,price=limit_price, side = side)
        self.ssl_sock.sendall(message)
        diff = (datetime.now() - start_time).total_seconds()
        print(message.replace(b'\x01', b'|'))
        self.seq_num +=1
        return (cl_oid,self.handle_execution_report(cl_oid=cl_oid, type="new_order"), diff)



    def handle_login_message(self):
        print("Logging In")
        print(self.ssl_sock.recv(4096).replace(b'\x01', b'|'))

        
        
        



    def handle_execution_report(self,cl_oid, type):

        exectype = 0
        exectype2 = ""
        if type == 'new_order':
            exectype = "A"
            exectype2 = "0"
        elif type == 'cancel_order':
            exectype = "6"
            exectype2 = "4"
        elif type == 'query_order':
            exectype = "I"
            

        resp = self.ssl_sock.recv(4096).replace(b'\x01', b'|')
        print(resp)
        resp = (str(resp)).split("|")

        resp_dict = {}
        i = 0
        for token in resp:
            if i == len(resp) - 1:
                break
            temp = token.split('=')
            resp_dict[temp[0]] = temp[1]
            i+=1
        

        
        while(resp_dict['35'] == '0' or resp_dict['35'] == '1' or (resp_dict['35'] == '8' and (('11' in resp_dict and resp_dict['11'] != str(cl_oid)) or (resp_dict['150']!= exectype and resp_dict['150']!= exectype2)))):
            # If test request 
            if (resp_dict['35'] == '1'):
                treqid = resp_dict['112']
                self.send_heartbeat(treqid)

            # Print response, receive next and parse
            resp = self.ssl_sock.recv(4096).replace(b'\x01', b'|')
            print(resp)
            resp = (str(resp)).split("|")

            resp_dict = {}
            i = 0
            for token in resp:
                if i == len(resp) - 1:
                    break
                temp = token.split('=')
                resp_dict[temp[0]] = temp[1]
                i+=1
            print("in loop")
            print(exectype)
        print("exited loop")
        oid = 0
        if resp_dict['35'] == '8':
            if type == 'cancel_order':
                print("Cancelled order")
                return "Cancelled Order"
            elif type == 'new_order':
                oid = int(resp_dict['37'])
                print(oid)
                return oid
            elif type == 'query_order':
                oid = int(resp_dict['37'])
                return_dict = {}
                return_dict["filled_size"] = float(resp_dict['14'])
                return_dict["remaining_size"] = float(resp_dict['151'])
                return_dict["oid"] = int(resp_dict['37'])
                return_dict["status"] = str(resp_dict['39'])
                return return_dict
        elif resp_dict['35'] == '9':
            return "Couldn't cancel in time"


    def cancel_order(self, cl_oid):
        print("trying to cancel order")
        t = str(datetime.utcnow()).replace("-","").replace(" ", "-")[:-7]
        message = self.generate_cancel_order_string(self.seq_num,t,self.api_key,cl_oid=cl_oid)
        self.ssl_sock.sendall(message)
        self.seq_num+=1
        return self.handle_execution_report(cl_oid=cl_oid, type="cancel_order")
        


    def query_order(self, cl_oid):
        print("querying order")
        t = str(datetime.utcnow()).replace("-","").replace(" ", "-")[:-7]
        message = self.generate_query_order_string(self.seq_num,t,self.api_key,cl_oid=cl_oid)
        self.ssl_sock.sendall(message)
        self.seq_num+=1
        return self.handle_execution_report(cl_oid=cl_oid, type="query_order")
        







