import io
import os
import requests
import base64
import uuid
from pathlib import Path
from urllib.parse import urlparse
from PIL import Image
from typing import Dict, List

import jingrow


def smart_truncate(text: str, max_length: int) -> str:
	"""
	智能截断文本，避免截断单词或汉字。
	策略：优先在标点截断，其次空格，最后硬截断；至少保留 70% 内容。
	"""
	if not text or len(text) <= max_length:
		return text

	truncated = text[:max_length]
	# 优先标点
	for punct in ['。', '！', '？', '.', '!', '?', '；', ';', '，', ',', '：', ':']:
		last_punct = truncated.rfind(punct)
		if last_punct > max_length * 0.7:
			return truncated[: last_punct + 1]
	# 其次空格（避免截断英文单词）
	last_space = truncated.rfind(' ')
	if last_space > max_length * 0.7:
		return truncated[: last_space]
	# 都没有则硬截断
	return truncated

def get_jingrow_api_headers():
    api_key = jingrow.db.get_single_value("Ai Settings", "jingrow_api_key")
    ai_settings = jingrow.get_single("Ai Settings")
    api_secret = ai_settings.get_password("jingrow_api_secret")
    
    if not api_key or not api_secret:
        return None
        
    return {
        "Authorization": f"token {api_key}:{api_secret}",
        "Content-Type": "application/json"
    }

def update_progress(pg_name, progress_value, pagetype):

    try:
        pg = jingrow.get_pg(pagetype, pg_name)
        if pg and hasattr(pg, "progress"):
            pg.progress = progress_value
            pg.save()
            jingrow.db.commit()
    except Exception as e:
        jingrow.log_error("更新进度失败", f"记录: {pg_name}, 错误: {str(e)}")

def send_notification(message, title="任务未完成", user=None):

    try:
        # 如果未指定用户，使用当前会话用户
        if not user:
            user = jingrow.session.user
        
        # 使用msgprint事件，前端会自动处理并显示弹窗
        jingrow.publish_realtime(
            event="msgprint",
            message={
                "title": title,
                "message": message,
                "indicator": "red"
            },
            user=user,
            after_commit=True
        )
                    
    except Exception:
        pass

def upload_image(record_name, image_data, image_filename, pagetype):

    try:
        # 创建文件并关联到记录
        file_pg = jingrow.get_pg(
            {
                "pagetype": "File",
                "attached_to_pagetype": pagetype,
                "attached_to_name": record_name,
                "file_name": image_filename,
                "is_private": 0,
                "content": image_data
            }
        )
        file_pg.save()
        jingrow.db.commit()
        
        # 返回文件URL
        return file_pg.file_url
    except Exception:
        return None

def is_valid_image_url(url):

    return url and url.strip() and url.lower() != "none"

def upload_image_to_intermediate_server(image_url, upload_url):

    try:        
        response = requests.get(image_url, verify=False)
        image_data = response.content

        parsed_url = urlparse(image_url)
        file_name = Path(parsed_url.path).name
        file_ext = Path(file_name).suffix

        # 如果图片是webp格式，转换为png格式
        if file_ext.lower() == '.webp':
            image = Image.open(io.BytesIO(image_data))
            png_buffer = io.BytesIO()
            image.save(png_buffer, format='PNG')
            image_data = png_buffer.getvalue()
            file_name = file_name.replace('.webp', '.png')

        files = {"file": (file_name, image_data)}

        upload_response = requests.post(upload_url, files=files, verify=False)

        if upload_response.status_code == 200:
            return upload_response.json()["file_url"]
        else:
            jingrow.log_error("上传图片失败", f"状态码: {upload_response.status_code}, 响应: {upload_response.text[:200]}")
            raise Exception(f"上传失败. 状态码: {upload_response.status_code}, {upload_response.text}")
            
    except Exception as e:
        jingrow.log_error("上传图像到中间服务器失败", f"错误: {str(e)}, URL: {image_url}")
        raise Exception(f"上传图像到中间服务器失败: {str(e)}")

@jingrow.whitelist()
def clear_current_page_images(pg_name, pagetype):

	try:
		# 获取页面记录
		pg = jingrow.get_pg(pagetype, pg_name)
		if not pg:
			return {"message": "未找到相关记录"}

		# 获取所有关联的文件
		file_pgs = jingrow.db.get_all(
			"File",
			{"attached_to_pagetype": pagetype, "attached_to_name": pg_name},
			["name", "file_name", "file_type"]
		)

		# 定义图片文件类型
		image_types = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
		
		# 删除所有图片文件
		deleted_count = 0
		for file_pg in file_pgs:
			file_ext = os.path.splitext(file_pg.file_name)[1].lower()
			if file_ext in image_types:
				try:
					jingrow.delete_pg("File", file_pg.name)
					deleted_count += 1
				except Exception as e:
					jingrow.log_error(f"删除文件失败: {file_pg.file_name}", str(e))

		# 保存并提交数据库触发前端刷新
		pg.save(ignore_permissions=True)
		jingrow.db.commit()
		
		return {
			"message": f"成功删除 {deleted_count} 个图片文件",
			"deleted_count": deleted_count
		}

	except Exception as e:
		error_msg = f"清空图片失败: {str(e)}"
		jingrow.log_error("清空图片失败", str(e))
		return {"message": error_msg, "error": True}

def get_jingrow_api_url():
    """获取 Jingrow API 基础 URL
    
    Returns:
        str: API 基础 URL
    """
    return jingrow.db.get_single_value("Ai Settings", "jingrow_api_url") or "https://api.jingrow.com"

def update_status_progress(pagetype, pg_name, status, progress):
    """更新状态和进度
    
    Args:
        pagetype (str): 页面类型
        pg_name (str): 页面名称
        status (str): 状态值
        progress (int): 进度值 (0-100)
    """
    try:
        # 状态更新使用 set_value，避免触发钩子
        jingrow.db.set_value(pagetype, pg_name, "status", status)
        jingrow.db.commit()
        
        # 进度更新使用 save，更新后触发页面自动刷新
        pg = jingrow.get_pg(pagetype, pg_name)
        pg.progress = progress
        pg.save()
        jingrow.db.commit()
    except Exception as e:
        jingrow.log_error("状态更新错误", f"更新状态失败: {str(e)}")

def get_new_image_url(image_url):

    try:        
        upload_url = jingrow.db.get_single_value("Ai Settings", "upload_url")
        # 下载图片
        response = requests.get(image_url, verify=False)
        image_data = response.content

        # 解析文件名和扩展名
        parsed_url = urlparse(image_url)
        file_name = Path(parsed_url.path).name
        file_ext = Path(file_name).suffix

        # 如果图片是webp格式，转换为png格式
        if file_ext.lower() == '.webp':
            image = Image.open(io.BytesIO(image_data))
            png_buffer = io.BytesIO()
            image.save(png_buffer, format='PNG')
            image_data = png_buffer.getvalue()
            file_name = file_name.replace('.webp', '.png')

        # 准备文件上传
        files = {"file": (file_name, image_data)}

        # 上传图片
        upload_response = requests.post(upload_url, files=files, verify=False)

        if upload_response.status_code == 200:
            result = upload_response.json()
            return result.get("url")
        else:
            error_message = f"图片URL转换失败: 状态码 {upload_response.status_code}, 响应: {upload_response.text[:200]}"
            jingrow.log_error("图片URL转换失败", error_message)
            raise Exception(error_message)
            
    except Exception as e:
        error_message = f"图片URL转换异常: {str(e)}"
        jingrow.log_error("图片URL转换异常", error_message)
        raise Exception(error_message)

# =====================
# 以下函数迁移自 ai_agent.py，后续逐步替换原有调用，待ai_agent.py不再使用后可删除原实现。
# =====================
@jingrow.whitelist()
def call_ai_model(prompt, text_model=None, ai_temperature=None, ai_top_p=None, ai_system_message=None, max_tokens=None, image_urls=None):
    """
    统一的AI模型调用入口（通用工具函数）
    Args:
        prompt (str): 提示文本
        text_model (str): 模型类型
        ai_temperature (float): 温度参数
        ai_top_p (float): top_p参数
        ai_system_message (str): 系统消息
        max_tokens (int): 最大token数
        image_urls (List[str], optional): 图片URL列表
    Returns:
        str: AI模型的响应文本
    """
    try:
        processed_image_urls = []
        if image_urls:
            for img_url in image_urls:
                if is_valid_image_url(img_url):
                    try:
                        processed_url = get_new_image_url(img_url)
                        if processed_url:
                            processed_image_urls.append(processed_url)
                    except Exception:
                        pass
        model_type = (text_model or "jingrow").lower()
        if "jingrow" in model_type:
            return call_jingrow_model(prompt, processed_image_urls, ai_temperature, ai_top_p, ai_system_message, max_tokens)
        elif "deepseek" in model_type:
            return _call_deepseek_model(prompt, processed_image_urls, ai_temperature, ai_top_p, ai_system_message, max_tokens)
        elif "doubao" in model_type:
            return _call_doubao_model(prompt, processed_image_urls, ai_temperature, ai_top_p, ai_system_message, max_tokens)
        else:
            return _call_chatgpt_model(prompt, processed_image_urls, ai_temperature, ai_top_p, ai_system_message, max_tokens)
    except Exception:
        return None

def call_jingrow_model(prompt, image_urls=None, ai_temperature=None, ai_top_p=None, ai_system_message=None, max_tokens=None):
    try:
        api_url = f"{get_jingrow_api_url()}/jchat/chat"
        headers = get_jingrow_api_headers()
        if not headers:
            send_notification("Jingrow API 未设置，请在 AI设置 中设置 Jingrow Api Key 和 Jingrow Api Secret")
            return None
        temperature = ai_temperature or jingrow.db.get_single_value("Ai Settings", "temperature") or 0.7
        top_p = ai_top_p or jingrow.db.get_single_value("Ai Settings", "top_p") or 0.9
        system_message = ai_system_message or "你是一个有用的AI助手。"
        if image_urls and len(image_urls) > 0:
            vision_model = jingrow.db.get_single_value("Ai Settings", "jingrow_image_to_text_model") or "jingrow-chat-vision"
            model = vision_model
            content = []
            content.append({"type": "text", "text": prompt})
            for img_url in image_urls:
                if img_url:
                    content.append({"type": "image_url", "image_url": {"url": img_url}})
            payload = {
                "model": model,
                "messages": [
                    {"role": "system", "content": system_message},
                    {"role": "user", "content": content}
                ],
                "temperature": temperature,
                "top_p": top_p,
                "max_tokens": max_tokens or 2048
            }
        else:
            model = jingrow.db.get_single_value("Ai Settings", "jingrow_api_model") or "jingrow-chat"
            payload = {
                "model": model,
                "messages": [
                    {"role": "system", "content": system_message},
                    {"role": "user", "content": prompt}
                ],
                "temperature": temperature,
                "top_p": top_p,
                "max_tokens": max_tokens or 2048
            }
        resp = requests.post(api_url, json=payload, headers=headers)
        if resp.status_code != 200:
            return None
        try:
            resp_json = resp.json()
            message = resp_json.get("choices", [{}])[0].get("message", {}).get("content", "")
            if not message:
                data = resp_json.get("data") or (resp_json.get("message") or {}).get("data")
                if isinstance(data, dict):
                    message = data.get("content", "")
                elif isinstance(data, str):
                    message = data
            return message
        except Exception:
            return None
    except Exception:
        return None

def _call_chatgpt_model(prompt, image_urls=None, ai_temperature=None, ai_top_p=None, ai_system_message=None, max_tokens=None):
    try:
        api_url = jingrow.db.get_single_value("Ai Settings", "chatgpt_api_url")
        api_key = jingrow.db.get_single_value("Ai Settings", "chatgpt_api_key")
        model = jingrow.db.get_single_value("Ai Settings", "chatgpt_api_model") or "gpt-4o"
        content = [{"type": "text", "text": prompt}]
        if image_urls:
            for img_url in image_urls:
                if img_url:
                    content.append({"type": "image_url", "image_url": {"url": img_url}})
        system_message = ai_system_message or "你是一个有用的AI助手。"
        temperature = ai_temperature or jingrow.db.get_single_value("Ai Settings", "temperature") or 0.7
        top_p = ai_top_p or jingrow.db.get_single_value("Ai Settings", "top_p") or 0.9
        payload = {
            "model": model,
            "messages": [
                {"role": "system", "content": system_message},
                {"role": "user", "content": content}
            ],
            "temperature": temperature,
            "top_p": top_p,
            "max_tokens": max_tokens or 2048
        }
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }
        response = requests.post(api_url, headers=headers, json=payload)
        if response.status_code != 200:
            return None
        try:
            result = response.json()
            message = result.get("choices", [{}])[0].get("message", {}).get("content", "")
            return message
        except Exception:
            return None
    except Exception:
        return None

def _call_deepseek_model(prompt, image_urls=None, ai_temperature=None, ai_top_p=None, ai_system_message=None, max_tokens=None):
    try:
        api_url = jingrow.db.get_single_value("Ai Settings", "deepseek_api_url")
        ai_settings = jingrow.get_single("Ai Settings")
        api_key = ai_settings.get_password("deepseek_api_key")
        model = jingrow.db.get_single_value("Ai Settings", "deepseek_api_model") or "deepseek-chat"
        temperature = ai_temperature or jingrow.db.get_single_value("Ai Settings", "temperature") or 0.7
        top_p = ai_top_p or jingrow.db.get_single_value("Ai Settings", "top_p") or 0.9
        max_tokens = max_tokens or jingrow.db.get_single_value("Ai Settings", "deepseek_api_max_token") or 2048
        system_message = ai_system_message or "你是一个有用的AI助手。"
        if image_urls and len(image_urls) > 0:
            vision_model = jingrow.db.get_single_value("Ai Settings", "deepseek_image_to_text_model") or "deepseek-chat"
            model = vision_model
            content = []
            content.append({"type": "text", "text": prompt})
            for img_url in image_urls:
                if img_url:
                    content.append({"type": "image_url", "image_url": {"url": img_url}})
            payload = {
                "model": model,
                "messages": [
                    {"role": "system", "content": system_message},
                    {"role": "user", "content": content}
                ],
                "temperature": temperature,
                "top_p": top_p,
                "max_tokens": max_tokens
            }
        else:
            payload = {
                "model": model,
                "messages": [
                    {"role": "system", "content": system_message},
                    {"role": "user", "content": prompt}
                ],
                "temperature": temperature,
                "top_p": top_p,
                "max_tokens": max_tokens
            }
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }
        response = requests.post(api_url, headers=headers, json=payload)
        if response.status_code != 200:
            return None
        try:
            result = response.json()
            message = result.get("choices", [{}])[0].get("message", {}).get("content", "")
            return message
        except Exception:
            return None
    except Exception:
        return None

def _call_doubao_model(prompt, image_urls=None, ai_temperature=None, ai_top_p=None, ai_system_message=None, max_tokens=None):
    try:
        api_url = jingrow.db.get_single_value("Ai Settings", "doubao_api_url")
        ai_settings = jingrow.get_single("Ai Settings")
        api_key = ai_settings.get_password("doubao_api_key")
        model = jingrow.db.get_single_value("Ai Settings", "doubao_api_model") or "doubao-pro-32k-241215"
        temperature = ai_temperature or jingrow.db.get_single_value("Ai Settings", "temperature") or 0.7
        top_p = ai_top_p or jingrow.db.get_single_value("Ai Settings", "top_p") or 0.9
        system_message = ai_system_message or "你是一个有用的AI助手。"
        if image_urls and len(image_urls) > 0:
            content = []
            content.append({"type": "text", "text": prompt})
            for img_url in image_urls:
                if img_url:
                    content.append({"type": "image_url", "image_url": {"url": img_url}})
            vision_model = jingrow.db.get_single_value("Ai Settings", "doubao_image_to_text_model") or "doubao-1.5-vision-pro-250328"
            model = vision_model
            payload = {
                "model": model,
                "messages": [
                    {"role": "system", "content": system_message},
                    {"role": "user", "content": content}
                ],
                "temperature": temperature,
                "top_p": top_p,
                "max_tokens": max_tokens or 2048
            }
        else:
            payload = {
                "model": model,
                "messages": [
                    {"role": "system", "content": system_message},
                    {"role": "user", "content": prompt}
                ],
                "temperature": temperature,
                "top_p": top_p,
                "max_tokens": max_tokens or 2048
            }
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }
        response = requests.post(api_url, headers=headers, json=payload)
        if response.status_code != 200:
            return None
        try:
            result = response.json()
            message = result.get("choices", [{}])[0].get("message", {}).get("content", "")
            return message
        except Exception:
            return None
    except Exception:
        return None
# =====================
# END: 迁移自 ai_agent.py
# =====================
def async_update_field(pagetype, pg_name, field, value, write_mode="overwrite", append_prefix=","):
    """异步更新指定字段，避免并行写入冲突"""
    jingrow.enqueue(
        "jingrow.ai.utils.utils._execute_field_update",
        pagetype=pagetype,
        pg_name=pg_name,
        field=field,
        value=value,
        write_mode=write_mode,
        append_prefix=append_prefix
    )

def _execute_field_update(pagetype, pg_name, field, value, write_mode="overwrite", append_prefix=","):
    """执行字段更新的后台任务"""
    try:
        # 获取记录实例
        pg = jingrow.get_pg(pagetype, pg_name)
        if not pg:
            return
            
        if write_mode == "append":
            current_value = getattr(pg, field, "") or ""
            current_str = str(current_value).strip()
            new_value_str = str(value).strip()
            
            # 检查是否重复：将当前值按分隔符分割，检查新值是否已存在
            if current_str:
                # 按分隔符分割当前值
                current_values = [v.strip() for v in current_str.split(append_prefix) if v.strip()]
                # 检查新值是否已存在
                if new_value_str in current_values:
                    # 值已存在，不追加
                    return
                
                # 值不存在，进行追加
                if current_str.endswith(append_prefix):
                    new_value = current_str + new_value_str
                else:
                    new_value = current_str + append_prefix + new_value_str
            else:
                # 当前值为空，直接设置新值
                new_value = new_value_str
                
            setattr(pg, field, new_value)
            pg.save(ignore_permissions=True)
            jingrow.db.commit()
            
        else:
            setattr(pg, field, str(value))
            pg.save(ignore_permissions=True)
            jingrow.db.commit()
            
    except Exception as e:
        jingrow.log_error("异步字段更新失败", f"记录: {pg_name}, 字段: {field}, 值: {value}, 错误: {str(e)}")

@jingrow.whitelist()
def get_field_select_options(pagetype: str, fieldname: str) -> Dict:
    """通用：读取 PageType 中某个 Select 字段的 options，多行字符串→数组"""
    try:
        if not pagetype or not fieldname:
            return {"options": []}
        meta = jingrow.get_meta(pagetype)
        field = meta.get_field(fieldname)
        raw_opts = getattr(field, 'options', None)
        if isinstance(raw_opts, str):
            options = [opt.strip() for opt in raw_opts.split('\n') if opt and opt.strip()]
        else:
            options = []
        return {"options": options}
    except Exception as e:
        jingrow.log_error(f"获取下拉选项失败: {pagetype}.{fieldname}", str(e))
        return {"options": []}
