import os
import numpy as np
import cv2
import gc
import time
import threading
from PIL import Image
from insightface.app import FaceAnalysis
# 导入获取人脸信息的服务
from service.face_service import get_all_face_name_with_eigenvalue

# 用于检查GPU状态
try:
    import pynvml

    pynvml.nvmlInit()
    _nvml_available = True
except ImportError:
    print("警告: pynvml库未安装、无法检测GPU状态、将默认使用0号GPU")
    _nvml_available = False

# 全局变量
_face_app = None
_known_faces_embeddings = {}  # 存储姓名到特征值的映射
_known_faces_names = []  # 存储所有已知姓名
_using_gpu = False  # 标记是否使用GPU
_used_gpu_id = -1  # 记录当前使用的GPU ID

# 资源管理变量
_ref_count = 0
_last_used_time = 0
_lock = threading.Lock()
_release_timeout = 8  # 5秒无使用则释放
_is_releasing = False  # 标记是否正在释放

# 调试用计数器
_debug_counter = {
    "created": 0,
    "released": 0,
    "detected": 0
}


def check_gpu_availability(gpu_id, threshold=0.7):
    """检查指定GPU是否可用（内存使用率低于阈值）"""
    if not _nvml_available:
        return True  # 无法检测时默认认为可用

    try:
        handle = pynvml.nvmlDeviceGetHandleByIndex(gpu_id)
        mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
        usage = mem_info.used / mem_info.total
        # 内存使用率低于阈值则认为可用
        return usage < threshold
    except Exception as e:
        print(f"检查GPU {gpu_id} 状态时出错: {e}")
        return False


def select_best_gpu(preferred_gpus=[0, 1]):
    """选择最佳可用GPU、严格按照首选列表顺序检查、优先使用0号GPU"""
    # 首先检查首选GPU列表
    for gpu_id in preferred_gpus:
        try:
            # 检查GPU是否存在
            if _nvml_available:
                pynvml.nvmlDeviceGetHandleByIndex(gpu_id)

            # 检查GPU是否可用
            if check_gpu_availability(gpu_id):
                print(f"GPU {gpu_id} 可用、将使用该GPU")
                return gpu_id
            else:
                if gpu_id == 0:
                    print(f"GPU 0 内存使用率过高（繁忙）、尝试切换到其他GPU")
        except Exception as e:
            print(f"GPU {gpu_id} 不存在或无法访问: {e}")
            continue

    # 如果所有首选GPU都不可用、返回-1表示使用CPU
    print("所有指定的GPU都不可用、将使用CPU进行计算")
    return -1


def _release_engine():
    """释放人脸识别引擎资源"""
    global _face_app, _is_releasing, _known_faces_embeddings, _known_faces_names
    if not _face_app or _is_releasing:
        return

    try:
        _is_releasing = True
        # 释放InsightFace资源
        if hasattr(_face_app, 'model'):
            # 清除模型资源
            _face_app.model = None
        _face_app = None

        # 清空人脸数据
        _known_faces_embeddings.clear()
        _known_faces_names.clear()

        _debug_counter["released"] += 1
        print(f"Face recognition engine released. Stats: {_debug_counter}")

        # 清理GPU缓存
        gc.collect()
        try:
            import torch
            if torch.cuda.is_available():
                torch.cuda.empty_cache()
                torch.cuda.ipc_collect()
        except ImportError:
            pass
        try:
            import tensorflow as tf
            tf.keras.backend.clear_session()
        except ImportError:
            pass
    finally:
        _is_releasing = False


def _monitor_thread():
    """监控线程、检查并释放超时未使用的资源"""
    global _ref_count, _last_used_time, _face_app
    while True:
        time.sleep(5)  # 每5秒检查一次
        with _lock:
            # 只有当引擎存在、没有引用且超时、才释放
            if _face_app and _ref_count == 0 and not _is_releasing:
                elapsed = time.time() - _last_used_time
                if elapsed > _release_timeout:
                    print(f"Idle timeout ({elapsed:.1f}s > {_release_timeout}s), releasing face engine")
                    _release_engine()


def load_model(prefer_gpu=True, preferred_gpus=[0, 1]):
    """加载人脸识别模型及已知人脸特征库、默认优先使用0号GPU"""
    global _face_app, _known_faces_embeddings, _known_faces_names, _using_gpu, _used_gpu_id

    # 确保监控线程只启动一次
    if not any(t.name == "FaceMonitor" for t in threading.enumerate()):
        threading.Thread(target=_monitor_thread, daemon=True, name="FaceMonitor").start()
        print("Face monitor thread started")

    # 如果正在释放中、等待释放完成
    while _is_releasing:
        time.sleep(0.1)

    # 如果已经初始化、直接返回
    if _face_app:
        return True

    # 初始化InsightFace模型
    try:
        # 初始化InsightFace
        print("正在初始化InsightFace人脸识别引擎...")
        _face_app = FaceAnalysis(name='buffalo_l', root='~/.insightface')

        # 选择合适的GPU、默认优先使用0号
        ctx_id = 0
        if prefer_gpu:
            ctx_id = select_best_gpu(preferred_gpus)
            _using_gpu = ctx_id != -1
            _used_gpu_id = ctx_id if _using_gpu else -1

            if _using_gpu:
                print(f"成功初始化、使用GPU {ctx_id} 进行计算")
            else:
                print("成功初始化、使用CPU进行计算")

        # 准备模型
        _face_app.prepare(ctx_id=ctx_id, det_size=(640, 640))
        print("InsightFace人脸识别引擎初始化成功。")
        _debug_counter["created"] += 1
        print(f"Face engine initialized. Stats: {_debug_counter}")

    except Exception as e:
        print(f"初始化失败: {e}")
        return False

    # 从服务获取所有人脸姓名和特征值
    try:
        face_data = get_all_face_name_with_eigenvalue()

        # 处理获取到的人脸数据
        for person_name, eigenvalue_data in face_data.items():
            # 处理特征值数据 - 兼容数组和字符串两种格式
            if isinstance(eigenvalue_data, np.ndarray):
                # 如果已经是numpy数组、直接使用
                eigenvalue = eigenvalue_data.astype(np.float32)
            elif isinstance(eigenvalue_data, str):
                # 清理字符串: 移除方括号、换行符和多余空格
                cleaned = eigenvalue_data.replace('[', '').replace(']', '').replace('\n', '').strip()
                # 按空格或逗号分割（处理可能的不同分隔符）
                values = [v for v in cleaned.split() if v]
                # 转换为数组
                eigenvalue = np.array(list(map(float, values)), dtype=np.float32)
            else:
                # 不支持的类型
                print(f"Unsupported eigenvalue type for {person_name}")
                continue

            # 归一化处理
            norm = np.linalg.norm(eigenvalue)
            if norm != 0:
                eigenvalue = eigenvalue / norm

            _known_faces_embeddings[person_name] = eigenvalue
            _known_faces_names.append(person_name)

    except Exception as e:
        print(f"Error loading face data from service: {e}")

    return True if _face_app else False


def detect(frame, threshold=0.4):
    """检测并识别人脸、返回结果元组(是否匹配到已知人脸, 结果字符串)"""
    global _face_app, _known_faces_embeddings, _known_faces_names, _using_gpu, _used_gpu_id
    global _ref_count, _last_used_time

    # 验证前置条件
    if frame is None or frame.size == 0:
        return (False, "无效帧数据")

    # 增加引用计数并获取引擎实例
    engine = None
    with _lock:
        _ref_count += 1
        _last_used_time = time.time()
        _debug_counter["detected"] += 1

        # 初始化引擎（如果未初始化且不在释放中）
        if not _face_app and not _is_releasing:
            if not load_model(prefer_gpu=True):
                _ref_count -= 1  # 恢复引用计数
                return (False, "引擎初始化失败")

        # 获取当前引擎引用
        engine = _face_app

    # 检查引擎是否可用
    if not engine or not _known_faces_names:
        with _lock:
            _ref_count = max(0, _ref_count - 1)
        return (False, "人脸识别引擎不可用或未初始化")

    try:
        # 如果使用GPU、确保输入帧在处理前是连续的数组
        if _using_gpu and not frame.flags.contiguous:
            frame = np.ascontiguousarray(frame)

        faces = _face_app.get(frame)
    except Exception as e:
        print(f"Face detect error: {e}")
        # 检测到错误时尝试重新选择GPU并重新初始化
        print("尝试重新选择GPU并重新初始化...")
        with _lock:
            _ref_count = max(0, _ref_count - 1)
        load_model(prefer_gpu=True)  # 重新初始化时保持默认GPU优先级
        return (False, f"检测错误: {str(e)}")

    result_parts = []
    has_matched = False  # 标记是否有匹配到的已知人脸

    for face in faces:
        # 特征归一化
        embedding = face.embedding.astype(np.float32)
        norm = np.linalg.norm(embedding)
        if norm == 0:
            continue
        embedding = embedding / norm

        # 对比已知人脸
        max_sim, best_name = -1.0, "Unknown"
        for name in _known_faces_names:
            known_emb = _known_faces_embeddings[name]
            sim = np.dot(embedding, known_emb)
            if sim > max_sim:
                max_sim = sim
                best_name = name

        # 判断匹配结果
        is_match = max_sim >= threshold
        if is_match:
            has_matched = True  # 只要有一个匹配成功、就标记为True

        bbox = face.bbox
        result_parts.append(
            f"{'匹配' if is_match else '不匹配'}: {best_name} (相似度: {max_sim:.2f}, 边界框: {bbox})"
        )

    # 构建结果字符串
    if not result_parts:
        result_str = "未检测到人脸"
    else:
        result_str = "; ".join(result_parts)

    # 减少引用计数、确保线程安全
    with _lock:
        _ref_count = max(0, _ref_count - 1)
        # 持续使用时更新最后使用时间
        if _ref_count > 0:
            _last_used_time = time.time()

    # 第一个返回值为: 是否匹配到已知人脸
    return (has_matched, result_str)