import bpy
import datetime
from typing import List
import asyncio
import functools

import requests

from .utils_debug import debug_err, debug_log, isDebugMode, isDevEnvironment

from .settings_addon import getAddonPreferences
from .async_loop import runAsync
from .utils_request_helper import runAsyncRequest

from .utils_json import jsonStringToObject
from .external_process import exec_command



__localServerUrl = "http://localhost:8424"
__last_time_started = 0
__baseCmd = f'npx --yes needle-cloud@version-1 '

def register():
    if isDevEnvironment():
        global __baseCmd
        debug_log("Running in dev environment - using license server channel 'next'")
        __baseCmd = f'npx --yes needle-cloud@next '

# for local debug:
# __baseCmd = "needle-cloud "


def ensureLicensingServerRuns(force:bool = False) -> bool:
    """
    Ensure the licensing server is running.
    Returns if the start command was executed.
    """
    global __last_time_started
    time = datetime.datetime.now().timestamp()
    if not force and time - __last_time_started < 60:
        return False
    __last_time_started = time
    cmd = __baseCmd + " start-server --integration blender"
    if isDebugMode():
        debug_log("Ensuring licensing server is running with command:", cmd)
    exec_command(cmd, onMessage=False, onClose=False)
    return True

__login_or_logout_clicked_time = 0
def login():
    global __login_or_logout_clicked_time
    __login_or_logout_clicked_time = datetime.datetime.now().timestamp()
    cmd = __baseCmd + " login"
    exec_command(cmd, onMessage=False, onClose=False)

def logout():
    global __login_or_logout_clicked_time
    __login_or_logout_clicked_time = datetime.datetime.now().timestamp()
    cmd = __baseCmd + " logout"
    exec_command(cmd, onMessage=False, onClose=False)


class UserInformationModel:
    class OrgInformationModel:
        id: str
        name: str
        canWrite: bool
        roles: List[str]

    authenticated: bool
    id: str
    name: str
    email: str
    profile_picture: str
    orgs: List[OrgInformationModel]

__userInformation: UserInformationModel = None
__lastUserInformationRequestTime = 0

def getUserInformation() -> UserInformationModel | None:
    """
    Get user information.
    """

    ensureLicensingServerRuns()


    global __userInformation

    global __lastUserInformationRequestTime
    time = datetime.datetime.now().timestamp()
    timeoutDuration = __userInformation == None or __userInformation.authenticated == False and 2 or 5
    if time - __lastUserInformationRequestTime < timeoutDuration:
        return __userInformation
    
    __lastUserInformationRequestTime = time
    url = __localServerUrl + "/api/user"

    def onUserInformation(res):
        global __userInformation
        if __userInformation == None:
            __userInformation = UserInformationModel()
        __userInformation.authenticated = False

        if res and res.ok: 
            __userInformation = jsonStringToObject(res.text)
        pass
    
    if isDebugMode():
        debug_log("Fetching user information")
    runAsyncRequest(url, onResponse=onUserInformation)
    return __userInformation




class LicenseInformationModel:
    user_id: str
    user_name: str | None
    user_email: str | None
    needle_engine_license: str | None
    needle_engine_license_is_active: bool | None
    needle_engine_license_status: str | None

__licenseInformation: LicenseInformationModel = None
__lastLicenseInformationRequestTime = 0

async def _fetch_license_async(url: str):
    global __licenseInformation
    loop = asyncio.get_event_loop()
    try:
        res = await loop.run_in_executor(None, functools.partial(requests.get, url))
        if res.ok:
            __licenseInformation = jsonStringToObject(res.text)
        # If not ok, __licenseInformation remains as it was (either None or the last successful fetch)
        # or if it was initialized to an empty model, it stays that way.
    except requests.exceptions.RequestException as e:
        # Handle exceptions if necessary, e.g., log them
        print(f"Error fetching license: {e}")
        debug_err("Error fetching license information:", e)
        # Potentially set __licenseInformation to a default/error state or leave as is
        pass


def getLicenses(force:bool = False) -> LicenseInformationModel | None:
    """
    Get user information. Non-blocking.
    """
    global __licenseInformation
    global __lastLicenseInformationRequestTime

    ensureLicensingServerRuns()

    # Initialize __licenseInformation if it's None, so callers always get a model instance.
    if __licenseInformation is None:
        __licenseInformation = LicenseInformationModel()

    now = datetime.datetime.now().timestamp()
    timeoutDuration = 2 if __licenseInformation is None or not getattr(__licenseInformation, 'needle_engine_license_is_active', False) else 5

    if not force:
        if now - __lastLicenseInformationRequestTime < timeoutDuration:
            return __licenseInformation
        
    prefs = getAddonPreferences();
    selected_team = prefs.selected_team
        
    __lastLicenseInformationRequestTime = now
    url = __localServerUrl + "/api/license?org=" + selected_team

    # Dispatch the async task
    runAsync(_fetch_license_async(url))

    return __licenseInformation





class NEEDLE_OT_Login(bpy.types.Operator):
    bl_idname = "needle.login"
    bl_label = "Login"
    bl_description = "Login to your Needle Account"
    def execute(self, context):
        login()
        return {"FINISHED"}

class NEEDLE_OT_Logout(bpy.types.Operator):
    bl_idname = "needle.logout"
    bl_label = "Logout"
    bl_description = "Logout of your Needle Account"
    def execute(self, context):
        logout()
        return {"FINISHED"}
    
def isWaitingForLoginOrLogout():
    now = datetime.datetime.now().timestamp()
    return now - __login_or_logout_clicked_time < 3

def drawLoginProcess():
    return False

