import wx
import thread
import logging
import threading
import copy
import uuid
import time

import eos.db
from eos.enum import Enum
from eos.types import CrestChar

import service

import gui.globalEvents as GE

logger = logging.getLogger(__name__)

class Servers(Enum):
    TQ = 0
    SISI = 1

class CrestModes(Enum):
    IMPLICIT = 0
    USER = 1

class Crest():

    clientIDs = {
        Servers.TQ: 'f9be379951c046339dc13a00e6be7704',
        Servers.SISI: 'af87365240d644f7950af563b8418bad'
    }

    # @todo: move this to settings
    clientCallback = 'http://localhost:6461'
    clientTest = True

    _instance = None
    @classmethod
    def getInstance(cls):
        if cls._instance == None:
            cls._instance = Crest()

        return cls._instance

    @classmethod
    def restartService(cls):
        # This is here to reseed pycrest values when changing preferences
        # We first stop the server n case one is running, as creating a new
        # instance doesn't do this.
        if cls._instance.httpd:
            cls._instance.stopServer()
        cls._instance = Crest()
        cls._instance.mainFrame.updateCrestMenus(type=cls._instance.settings.get('mode'))
        return cls._instance

    def __init__(self):
        """
        A note on login/logout events: the character login events happen
        whenever a characters is logged into via the SSO, regardless of mod.
        However, the mode should be send as an argument. Similarily,
        the Logout even happens whenever the character is deleted for either
        mode. The mode is sent as an argument, as well as the umber of
        characters still in the cache (if USER mode)
        """

        self.settings = service.settings.CRESTSettings.getInstance()
        self.scopes = ['characterFittingsRead', 'characterFittingsWrite']

        # these will be set when needed
        self.httpd = None
        self.state = None
        self.ssoTimer = None

        # Base EVE connection that is copied to all characters
        self.eve = service.pycrest.EVE(
            client_id=self.settings.get('clientID') if self.settings.get('mode') == CrestModes.USER else self.clientIDs.get(self.settings.get('server')),
            api_key=self.settings.get('clientSecret') if self.settings.get('mode') == CrestModes.USER else None,
            redirect_uri=self.clientCallback,
            testing=self.isTestServer
        )

        self.implicitCharacter = None

        # The database cache does not seem to be working for some reason. Use
        # this as a temporary measure
        self.charCache = {}

        # need these here to post events
        import gui.mainFrame  # put this here to avoid loop
        self.mainFrame = gui.mainFrame.MainFrame.getInstance()

    @property
    def isTestServer(self):
        return self.settings.get('server') == Servers.SISI

    def delCrestCharacter(self, charID):
        char = eos.db.getCrestCharacter(charID)
        del self.charCache[char.ID]
        eos.db.remove(char)
        wx.PostEvent(self.mainFrame, GE.SsoLogout(type=CrestModes.USER, numChars=len(self.charCache)))

    def delAllCharacters(self):
        chars = eos.db.getCrestCharacters()
        for char in chars:
            eos.db.remove(char)
        self.charCache = {}
        wx.PostEvent(self.mainFrame, GE.SsoLogout(type=CrestModes.USER, numChars=0))

    def getCrestCharacters(self):
        chars = eos.db.getCrestCharacters()
        # I really need to figure out that DB cache problem, this is ridiculous
        chars2 = [self.getCrestCharacter(char.ID) for char in chars]
        return chars2

    def getCrestCharacter(self, charID):
        '''
        Get character, and modify to include the eve connection
        '''
        if self.settings.get('mode') == CrestModes.IMPLICIT:
            if self.implicitCharacter.ID != charID:
                raise ValueError("CharacterID does not match currently logged in character.")
            return self.implicitCharacter

        if charID in self.charCache:
            return self.charCache.get(charID)

        char = eos.db.getCrestCharacter(charID)
        if char and not hasattr(char, "eve"):
            char.eve = copy.deepcopy(self.eve)
            char.eve.temptoken_authorize(refresh_token=char.refresh_token)
        self.charCache[charID] = char
        return char

    def getFittings(self, charID):
        char = self.getCrestCharacter(charID)
        return char.eve.get('%scharacters/%d/fittings/'%(char.eve._authed_endpoint,char.ID))

    def postFitting(self, charID, json):
        #@todo: new fitting ID can be recovered from Location header, ie: Location -> https://api-sisi.testeveonline.com/characters/1611853631/fittings/37486494/
        char = self.getCrestCharacter(charID)
        return char.eve.post('%scharacters/%d/fittings/'%(char.eve._authed_endpoint,char.ID), data=json)

    def delFitting(self, charID, fittingID):
        char = self.getCrestCharacter(charID)
        return char.eve.delete('%scharacters/%d/fittings/%d/'%(char.eve._authed_endpoint, char.ID, fittingID))

    def logout(self):
        """Logout of implicit character"""
        logging.debug("Character logout")
        self.implicitCharacter = None
        wx.PostEvent(self.mainFrame, GE.SsoLogout(type=self.settings.get('mode')))

    def stopServer(self):
        logging.debug("Stopping Server")
        self.httpd.stop()
        self.httpd = None

    def startServer(self):
        logging.debug("Starting server")
        if self.httpd:
            self.stopServer()
            time.sleep(1)  # we need this to ensure that the previous get_request finishes, and then the socket will close
        self.httpd = service.StoppableHTTPServer(('', 6461), service.AuthHandler)
        thread.start_new_thread(self.httpd.serve, (self.handleLogin,))

        self.state = str(uuid.uuid4())
        return self.eve.auth_uri(scopes=self.scopes, state=self.state)

    def handleLogin(self, message):
        if not message:
            return

        if message['state'][0] != self.state:
            logger.warn("OAUTH state mismatch")
            return

        logger.debug("Handling CREST login with: %s"%message)

        if 'access_token' in message:  # implicit
            eve = copy.deepcopy(self.eve)
            eve.temptoken_authorize(
                access_token=message['access_token'][0],
                expires_in=int(message['expires_in'][0])
            )
            self.ssoTimer = threading.Timer(int(message['expires_in'][0]), self.logout)
            self.ssoTimer.start()

            eve()
            info = eve.whoami()

            logger.debug("Got character info: %s" % info)

            self.implicitCharacter = CrestChar(info['CharacterID'], info['CharacterName'])
            self.implicitCharacter.eve = eve
            #self.implicitCharacter.fetchImage()

            wx.PostEvent(self.mainFrame, GE.SsoLogin(type=CrestModes.IMPLICIT))
        elif 'code' in message:
            eve = copy.deepcopy(self.eve)
            eve.authorize(message['code'][0])
            eve()
            info = eve.whoami()

            logger.debug("Got character info: %s" % info)

            # check if we have character already. If so, simply replace refresh_token
            char = self.getCrestCharacter(int(info['CharacterID']))
            if char:
                char.refresh_token = eve.refresh_token
            else:
                char = CrestChar(info['CharacterID'], info['CharacterName'], eve.refresh_token)
                char.eve = eve
            self.charCache[int(info['CharacterID'])] = char
            eos.db.save(char)

            wx.PostEvent(self.mainFrame, GE.SsoLogin(type=CrestModes.USER))

        self.stopServer()
