import bpy
import webbrowser
import requests
from typing import Optional
import os
import tempfile
import mimetypes
import time
import uuid
from urllib.parse import urlsplit, urlunsplit, parse_qs
from . import tos


def _show_error_dialog(message: str):
    try:
        bpy.ops.hitem3d.show_error('INVOKE_DEFAULT', message=message)
    except Exception:
        print(f"[Error] {message}")

def open_url(url: str):
    try:
        bpy.ops.wm.url_open(url=url)
    except Exception:
        webbrowser.open(url)


RES_LIMITS = {
    "512": 500000,
    "1536": 2000000,
    "1536pro": 2000000,
}

def get_face_limit_max(scene: Optional[bpy.types.Scene]) -> int:
    if not scene:
        return 2000000
    res = getattr(scene, "hitem3d_resolution", "1536")
    return RES_LIMITS.get(res, 2000000)

def generation(context, task_type: str, uid: str):
    url = "https://api.hitem3d.ai/open-api/v1/submit-task"
    scn = context.scene
    authorization = getattr(scn, "hitem3d_authorization", "")
    if not authorization:
        def show_auth_error():
            _show_error_dialog("Authorization is empty! Please confirm API keys.")
        bpy.app.timers.register(show_auth_error)
        return {"CANCELLED"}
    
    headers = {
        "Authorization": authorization,
        "User-Agent": "Apifox/1.0.0 (https://apifox.com)",
        "Accept": "*/*",
        "Host": "api.hitem3d.ai",
        "Connection": "keep-alive",
        "Accept-Language": bpy.app.translations.locale.replace("_", "-")
    }
    
    resolution = getattr(scn, "hitem3d_resolution", "1536")
    face = getattr(scn, "hitem3d_face_limit", 2000000)
    try:
        f_int = int(face)
    except Exception:
        f_int = 2000000
    f_int = max(100000, min(2000000, f_int))
    f_int = (f_int // 10000) * 10000
    model_type = getattr(scn, "hitem3d_model_type", "GENERAL")
    model_version = getattr(scn, "hitem3d_model_version", "v2_0")

    if model_type == "GENERAL":
        if model_version == "v2_0":
            model = "hitem3dv2.0"
        elif model_version == "v1_5":
            model = "hitem3dv1.5"
        else:
            model = "hitem3dv2.0"
    else:
        if model_version == "v2_1":
            model = "scene-portraitv2.1"
        elif model_version == "v2_0":
            model = "scene-portraitv2.0"
        elif model_version == "v1_5":
            model = "scene-portraitv1.5"
        else:
            model = "scene-portraitv2.0"
    data = {
            "request_type": "1",
            "resolution": resolution,
            "face": f_int,
            "model": model,
            "format": "4",
            "plug_type": "BLENDER",
            "plug_task_id": uid
        }

    try:
        if task_type == "image_to_model":
            pic_path = getattr(scn, "hitem3d_input_image_path", "")
            files = {
                "images": ("glove_1.jpg", open(pic_path, "rb"), "image/jpeg")
            }
        else:
            front_path = getattr(scn, "front_image_path", "")
            back_path = getattr(scn, "back_image_path", "")
            left_path = getattr(scn, "left_image_path", "")
            right_path = getattr(scn, "right_image_path", "")
            files = []
            multi_images_bit = ""
            if front_path:
                files.append(("multi_images", ("front.jpg", open(front_path, "rb"), "image/jpeg")))
                multi_images_bit += "1"
            else:
                multi_images_bit += "0"
            if back_path:
                files.append(("multi_images", ("back.jpg", open(back_path, "rb"), "image/jpeg")))
                multi_images_bit += "1"
            else:
                multi_images_bit += "0"
            if left_path:
                files.append(("multi_images", ("left.jpg", open(left_path, "rb"), "image/jpeg")))
                multi_images_bit += "1"
            else:
                multi_images_bit += "0"
            if right_path:
                files.append(("multi_images", ("right.jpg", open(right_path, "rb"), "image/jpeg")))
                multi_images_bit += "1"
            else:
                multi_images_bit += "0"
            
            data["multi_images_bit"] = multi_images_bit
            
            if not files:
                raise RuntimeError("No images selected for multi-view")
    except Exception as e:
        msg = f"Failed to prepare images: {e}"
        def show_prepare_error(m=msg):
            _show_error_dialog(m)
        bpy.app.timers.register(show_prepare_error)
        return {"CANCELLED"}
    
    response = requests.post(url, headers=headers, files=files, data=data)

    print(response.text)
    if response.json().get("code") != 200:
        msg = f"Failed to submit task: {response.text}"
        def show_submit_error(m=msg):
            _show_error_dialog(m)
        bpy.app.timers.register(show_submit_error)
        return {"CANCELLED"}
    status_url = "https://api.hitem3d.ai/open-api/v1/query-task?task_id=" + response.json().get("data", {}).get("task_id")
    
    Balance_url = "https://api.hitem3d.ai/open-api/v1/auth/balance"
    Balance_headers = {
        "Authorization": authorization,
        "User-Agent": "Apifox/1.0.0 (https://apifox.com)",
        "Accept": "*/*",
        "Host": "api.hitem3d.ai",
        "Connection": "keep-alive",
        "Accept-Language": bpy.app.translations.locale.replace("_", "-")
    }
    response = requests.request("GET", Balance_url, headers=Balance_headers)
    try:
        body = response.json()
    except Exception:
        self.report({'ERROR'}, "Failed to parse response")
        return {'CANCELLED'}
    if body.get("code") != 200:
        msg = body.get("msg") or "Unknown error"
        self.report({'ERROR'}, f"Failed to get balance: {msg}")
        return {'CANCELLED'}
    data = body.get("data", {}) if isinstance(body, dict) else {}
    balance = data.get("totalBalance") or 0
    scn.hitem3d_balance = float(balance)

    set_started = False
    set_queue = False
    set_done = False
    try:
        for _ in range(200):
            resp = requests.get(status_url, headers=headers)
            print(resp.text)
            try:
                obj = resp.json()
            except Exception:
                print(f"Failed to parse JSON: {resp.text}")
                break
            if obj.get("code") != 200:
                break
            data_obj = obj.get("data")
            state = data_obj.get("state")
            if state == "created":
                time.sleep(5)
                continue
            elif state == "queueing":
                if not set_queue:
                    def update_gen_geom_queue():
                        tasks = getattr(scn, "hitem3d_tasks", None)
                        if tasks is not None:
                            for i in range(len(tasks)):
                                if tasks[i].task_id == uid:
                                    tasks[i].gen_status = "QUEUE"
                                    break
                    bpy.app.timers.register(update_gen_geom_queue)
                    set_started = True
                time.sleep(5)
                continue
            elif state == "processing":
                if not set_started:
                    def update_gen_geom():
                        tasks = getattr(scn, "hitem3d_tasks", None)
                        if tasks is not None:
                            for i in range(len(tasks)):
                                if tasks[i].task_id == uid:
                                    tasks[i].gen_status = "GEN_GEOM"
                                    break
                    bpy.app.timers.register(update_gen_geom)
                    set_started = True
                time.sleep(5)
                continue
            elif state == "success":
                
                st = data_obj.get("url")
                if st is None:
                    msg = f"Failed to get model URL: {resp.text}"
                    def show_url_error(m=msg):
                        _show_error_dialog(m)
                    bpy.app.timers.register(show_url_error)
                    break
                ext = ".glb"
                tmpf = tempfile.NamedTemporaryFile(delete=False, suffix=ext)
                tmp_path = tmpf.name
                tmpf.close()
                
                r = requests.get(st, headers={}, stream=True, timeout=60)
                if r.status_code != 200:
                    raise RuntimeError(f"Download failed: {r.status_code}")
                with open(tmp_path, "wb") as out:
                    for chunk in r.iter_content(chunk_size=8192):
                        if chunk:
                            out.write(chunk)
                if ext in (".gltf", ".glb"):
                    def import_and_cleanup():
                        existing_objects = set(bpy.data.objects[:])
                        if bpy.context.mode != 'OBJECT':
                            bpy.ops.object.mode_set(mode='OBJECT')

                        bpy.ops.object.select_all(action='DESELECT')
                        bpy.ops.import_scene.fbx(filepath=tmp_path)
                        
                        new_objects = list(set(bpy.data.objects[:]) - existing_objects)
                        if new_objects:
                            active_col = bpy.context.view_layer.active_layer_collection.collection
                            for obj in new_objects:
                                obj.select_set(True)
                                
                                # Link to active collection if not present
                                if active_col not in obj.users_collection:
                                    active_col.objects.link(obj)
                                
                                # Unlink from other collections
                                for col in list(obj.users_collection):
                                    if col != active_col:
                                        col.objects.unlink(obj)
                                
                                # Rename object
                                obj.name = f"hitem3d_model_{uid}"
                            
                            # Set active object
                            bpy.context.view_layer.objects.active = new_objects[0]
                            
                            # Update task object reference
                            tasks = getattr(scn, "hitem3d_tasks", None)
                            if tasks is not None:
                                for i in range(len(tasks)):
                                    if tasks[i].task_id == uid:
                                        tasks[i].obj = new_objects[0]
                                        break
                                        
                        if os.path.exists(tmp_path):
                            os.remove(tmp_path)
                        return None
                        
                    # Schedule the import on the main thread
                    bpy.app.timers.register(import_and_cleanup)

                if not set_done:
                    def update_ready_tex():
                        tasks = getattr(scn, "hitem3d_tasks", None)
                        if tasks is not None:
                            for i in range(len(tasks)):
                                if tasks[i].task_id == uid:
                                    tasks[i].gen_status = "READY_TEX"
                                    break
                    bpy.app.timers.register(update_ready_tex)
                set_done = True
                return url        
            else:
                break
    except Exception as e:
        print(f"Request failed: {e}")
        return url
            
    return url

def texture(authorization, glb_path, image_path, task):
    scn = bpy.context.scene
    if not authorization:
        def show_auth_error():
            _show_error_dialog("Authorization is empty! Please confirm API keys.")
        bpy.app.timers.register(show_auth_error)
        if os.path.exists(glb_path):
            os.remove(glb_path)
        return {"CANCELLED"}
    
    token_url = "https://api.hitem3d.ai/open-api/v1/upload/token"
    token_headers = {
        "Authorization": authorization,
        "User-Agent": "Apifox/1.0.0 (https://apifox.com)",
        "Accept": "*/*",
        "Host": "api.hitem3d.ai",
        "Connection": "keep-alive",
        "Accept-Language": bpy.app.translations.locale.replace("_", "-")
    }
    
    response = requests.request("GET", token_url, headers=token_headers)

    try:
        body = response.json()
    except Exception as e:
        print(f"Request failed: {e}")
        if os.path.exists(glb_path):
            os.remove(glb_path)
        return {"CANCELLED"}
    if body.get('code') != 200:
        msg = body.get('message', 'Unknown error')
        print(msg)
        def show_error(m=msg):
            _show_error_dialog(f"Failed to get upload token: {m}")
        bpy.app.timers.register(show_error)
        if os.path.exists(glb_path):
            os.remove(glb_path)
        return {"CANCELLED"}
    data = body.get('data', {}) if isinstance(body, dict) else {}
    print(f"data is: {data}")

    ak = data.get('accessKeyId', None)
    sk = data.get('secretAccessKey', None)
    sessionKey = data.get('sessionKey', None)
    endpoint = "tos-ap-southeast-1.volces.com"
    region = "ap-southeast-1"

    bucket_name = "mm-sparc3d-prod"
    obj_id = str(uuid.uuid4())
    object_key = f"blender/mesh/{obj_id}.glb"

    try:
        print("Ready for upload mesh. ")
        client = tos.TosClientV2(ak, sk, endpoint, region, security_token=sessionKey)
        print("Initialized TOS client. ")
        out = client.pre_signed_url(tos.HttpMethodType.Http_Method_Put, bucket = bucket_name, key = object_key, expires = 3600)
        print(f"Pre-signed URL: {out.signed_url}")
        out = client.put_object_from_file(bucket_name, object_key, glb_path)
        print(f"Upload mesh response: {out.resp.resp.url}")
        print("Mesh uploaded successfully. ")
    except Exception as e:
        print(f"Failed to create TOS client: {e}")
        def show_client_error(m=str(e)):
            _show_error_dialog(f"Failed to create TOS client: {m}")
        bpy.app.timers.register(show_client_error)
        if os.path.exists(glb_path):
            os.remove(glb_path)
        return {"CANCELLED"}
    
    if os.path.exists(glb_path):
        os.remove(glb_path)
    url = "https://api.hitem3d.ai/open-api/v1/submit-task"
    headers = {
        "Authorization": authorization,
        "User-Agent": "Apifox/1.0.0 (https://apifox.com)",
        "Accept": "*/*",
        "Host": "api.hitem3d.ai",
        "Connection": "keep-alive",
        "Accept-Language": bpy.app.translations.locale.replace("_", "-")
    }

    try:
        mesh_url = "https://hitem3dstatic.zaohaowu.net/" + object_key
        print(f"mesh_url: {mesh_url}")
        data = {
            "request_type": "2",
            "model": "hitem3dv1.5",
            "format": "4",
            "face": "500000",
            "resolution": "512",
            "mesh_url": f"{mesh_url}",
            "plug_type": "BLENDER",
            "plug_task_id": task.task_id
        }
        print(data)
        image_file = open(image_path, "rb")
        files = {
            "images": ("texture.jpg", image_file, "image/jpeg")
        }
        response = requests.post(url, headers=headers, files=files, data=data)
        image_file.close()
        
        if response.status_code != 200:
            msg = f"HTTP {response.status_code}"
            print(msg)
            def show_http_error(m=msg):
                _show_error_dialog(f"Texture generation failed: {m}")
            bpy.app.timers.register(show_http_error)
            return {"CANCELLED"}
        print(response.text)
        try:
            body = response.json()
        except Exception:
            def show_parse_error():
                _show_error_dialog("Failed to parse response body.")
            bpy.app.timers.register(show_parse_error)
            return {"CANCELLED"}
        if body.get("code") != 200:
            msg = body.get("msg") or "Unknown error"
            def show_service_error(m=msg):
                _show_error_dialog(m)
            bpy.app.timers.register(show_service_error)
            
            return {"CANCELLED"}
        status_url = "https://api.hitem3d.ai/open-api/v1/query-task?task_id=" + body.get("data", {}).get("task_id")
        
        Balance_url = "https://api.hitem3d.ai/open-api/v1/auth/balance"
        Balance_headers = {
            "Authorization": authorization,
            "User-Agent": "Apifox/1.0.0 (https://apifox.com)",
            "Accept": "*/*",
            "Host": "api.hitem3d.ai",
            "Connection": "keep-alive",
            "Accept-Language": bpy.app.translations.locale.replace("_", "-")
        }
        response = requests.request("GET", Balance_url, headers=Balance_headers)
        try:
            body = response.json()
        except Exception:
            def show_balance_parse_error():
                _show_error_dialog("Failed to parse balance response")
            bpy.app.timers.register(show_balance_parse_error)
            return {"CANCELLED"}
        if body.get("code") != 200:
            msg = body.get("msg") or "Unknown error"
            def show_balance_error(m=msg):
                _show_error_dialog(f"Failed to get balance: {m}")
            bpy.app.timers.register(show_balance_error)
            return {"CANCELLED"}
        data = body.get("data", {}) if isinstance(body, dict) else {}
        balance = data.get("totalBalance") or 0
        scn.hitem3d_balance = float(balance)

        set_started = False
        set_queue = False
        set_done = False
        for _ in range(200):
            resp = requests.get(status_url, headers=headers)
            print(resp.text)
            try:
                obj = resp.json()
            except Exception:
                print(f"Failed to parse JSON: {resp.text}")
                break
            if obj.get("code") != 200:
                break
            data_obj = obj.get("data")
            state = data_obj.get("state")
            if state == "created":
                time.sleep(5)
                continue
            elif state == "queueing":
                if not set_queue:
                    def update_queue():
                        tasks = getattr(scn, "hitem3d_texture_tasks", None)
                        if tasks is not None:
                            for i in range(len(tasks)):
                                if tasks[i].task_id == task.task_id and tasks[i].create_time == task.create_time:
                                    tasks[i].tex_status = "QUEUE"
                                    break
                    bpy.app.timers.register(update_queue)
                    set_queue = True
                time.sleep(5)
                continue
            elif state == "processing":
                if not set_started:
                    def update_gen_tex():
                        tasks = getattr(scn, "hitem3d_texture_tasks", None)
                        if tasks is not None:
                            for i in range(len(tasks)):
                                if tasks[i].task_id == task.task_id and tasks[i].create_time == task.create_time:
                                    tasks[i].tex_status = "GEN_TEX"
                                    break
                    bpy.app.timers.register(update_gen_tex)
                    set_started = True
                time.sleep(5)
                continue
            elif state == "success":
                st = data_obj.get("url")
                if st is None:
                    def show_url_error():
                        _show_error_dialog(f"Failed to get model URL: {resp.text}")
                    bpy.app.timers.register(show_url_error)
                    break
                ext = ".glb"
                tmpf = tempfile.NamedTemporaryFile(delete=False, suffix=ext)
                tmp_path = tmpf.name
                tmpf.close()
                
                r = requests.get(st, headers={}, stream=True, timeout=60)
                if r.status_code != 200:
                    raise RuntimeError(f"Download failed: {r.status_code}")
                with open(tmp_path, "wb") as out:
                    for chunk in r.iter_content(chunk_size=8192):
                        if chunk:
                            out.write(chunk)
                if ext in (".gltf", ".glb"):
                    def import_and_cleanup_tex():
                        existing_objects = set(bpy.data.objects[:])
                        if bpy.context.mode != 'OBJECT':
                            bpy.ops.object.mode_set(mode='OBJECT')

                        bpy.ops.object.select_all(action='DESELECT')
                        bpy.ops.import_scene.fbx(filepath=tmp_path)
                        
                        new_objects = list(set(bpy.data.objects[:]) - existing_objects)
                        if new_objects:
                            active_col = bpy.context.view_layer.active_layer_collection.collection
                            for obj in new_objects:
                                obj.select_set(True)
                                
                                # Link to active collection if not present
                                if active_col not in obj.users_collection:
                                    active_col.objects.link(obj)
                                
                                # Unlink from other collections
                                for col in list(obj.users_collection):
                                    if col != active_col:
                                        col.objects.unlink(obj)
                                
                                # Rename object
                                obj.name = f"hitem3d_model_{task.task_id}"
                            
                            # Set active object
                            bpy.context.view_layer.objects.active = new_objects[0]
                            
                            # Update task object reference
                            scn = bpy.context.scene
                            tasks = getattr(scn, "hitem3d_texture_tasks", None)
                            if tasks is not None:
                                for i in range(len(tasks)):
                                    if tasks[i].task_id == task.task_id and tasks[i].create_time == task.create_time:
                                        tasks[i].obj = new_objects[0]
                                        break
                        return None
                    
                    # Schedule the import on the main thread
                    bpy.app.timers.register(import_and_cleanup_tex)

                if not set_done:
                    def update_done():
                        scn = bpy.context.scene
                        tasks = getattr(scn, "hitem3d_texture_tasks", None)
                        if tasks is not None:
                            for i in range(len(tasks)):
                                if tasks[i].task_id == task.task_id and tasks[i].create_time == task.create_time:
                                    tasks[i].tex_status = "DONE"
                                    break
                    bpy.app.timers.register(update_done)
                set_done = True
                return url        
            else:
                break

    except Exception as e:
        msg = f"Failed to save mesh: {e}"
        def show_except_error(m=msg):
            _show_error_dialog(m)
        bpy.app.timers.register(show_except_error)
        return {"CANCELLED"}
    
    return True
