# -*- coding: utf-8 -*-
"""
AI 图案生成接口
功能：生成预览图、面料图、裁切、细化
"""

from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from typing import Optional
import json, base64, logging
from app.core.config import settings
from app.core.security import get_current_user
from app.db import get_db
from sqlalchemy.orm import Session
from app.models.user import User
from app.api.v1.ai_llm import (
    call_image_model,
    call_image_model_batch,
    get_runtime_ai_config,
    has_ai_config,
)

log = logging.getLogger(__name__)
if not log.handlers:
    log.setLevel(logging.INFO)
    log.propagate = False
    _h = logging.StreamHandler()
    _h.setFormatter(
        logging.Formatter("[%(asctime)s][%(name)s][%(levelname)s] %(message)s")
    )
    log.addHandler(_h)

router = APIRouter()

# ==================== 数据模型 ====================


class GeneratePatternRequest(BaseModel):
    image_base64: str
    canvas_base64: Optional[str] = None
    prompt: Optional[str] = None
    image_edit_model: Optional[str] = None


class GenerateFabricRequest(BaseModel):
    image_base64: str
    fabric_info: dict
    image_edit_model: Optional[str] = None


class GenerateDesignImagesRequest(BaseModel):
    prompt: str
    reference_image_base64: Optional[str] = None
    count: int = 1
    size: Optional[str] = "2K"
    image_edit_model: Optional[str] = None


class CropPieceRequest(BaseModel):
    preview_base64: str
    canvas_width: int
    canvas_height: int
    piece_left: float
    piece_top: float
    piece_width: float
    piece_height: float
    piece_name: str
    padding: float = 0.15


class RefinePieceRequest(BaseModel):
    cropped_base64: str
    piece_name: str
    pattern_type: str = "fill_pattern"
    aspect_ratio: Optional[str] = None
    piece_width: Optional[int] = None
    piece_height: Optional[int] = None
    prompt: Optional[str] = None
    image_edit_model: Optional[str] = None


class ExtractPieceRequest(BaseModel):
    preview_base64: str
    piece_name: str
    piece_description: str = ""
    prompt: Optional[str] = None


# ==================== 图案生成接口 ====================


@router.post("/ai/generate-preview")
async def generate_preview(
    data: GeneratePatternRequest,
    current_username: str = Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """阶段1：成衣照片 + 裁片截图 → AI 生成带轮廓的花样预览图"""
    log.info(f"{'=' * 60}")
    log.info(
        f"[Preview] 收到预览请求, 成衣图: {len(data.image_base64) // 1024}KB, 裁片图: {len(data.canvas_base64) // 1024 if data.canvas_base64 else 0}KB"
    )
    if data.prompt:
        log.info(f"[Preview] 自定义提示词: {data.prompt[:150]}")
    user = db.query(User).filter(User.username == current_username).first()
    runtime_ai_config = get_runtime_ai_config(user)
    effective_image_model = (
        data.image_edit_model
        or (user.ai_image_model if user else None)
        or settings.AI_IMAGE_EDIT_MODEL
    )
    if not has_ai_config(runtime_ai_config) or not effective_image_model:
        raise HTTPException(400, "请先在 API 配置页填写转发 Key，或联系管理员配置默认 AI_API_KEY")

    try:
        images = [data.image_base64]
        if data.canvas_base64:
            images.append(data.canvas_base64)

        default_prompt = (
            (
                "Image 1 是一件成衣照片，Image 2 是裁片轮廓图。"
                "请将 Image 1 成衣上的面料花样提取出来，填充到 Image 2 的每个裁片轮廓内。"
                "要求：保持花样颜色、比例、方向一致；保留裁片轮廓线；不要改变裁片排列位置。"
            )
            if data.canvas_base64
            else ("请从这件成衣中提取面料花样，生成一张干净的花样平铺图。")
        )

        result_url, desc = call_image_model(
            images_b64=images,
            prompt=data.prompt or default_prompt,
            model_override=effective_image_model,
            runtime_config=runtime_ai_config,
        )
        return {"code": 200, "data": {"image_url": result_url, "description": desc}}
    except Exception as e:
        log.error(f"预览生成失败: {e}", exc_info=True)
        err_msg = str(e)
        if "data_inspection_failed" in err_msg or "inappropriate" in err_msg:
            raise HTTPException(
                400,
                "图片内容未通过平台安全审核，请更换图片后重试（避免使用含版权角色/敏感内容的图片）",
            )
        raise HTTPException(500, f"预览生成失败: {err_msg}")


@router.post("/ai/generate-fabric")
async def generate_fabric(
    data: GenerateFabricRequest,
    current_username: str = Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """all_over 模式：根据成衣照片 + fabric_info 生成面料平铺图"""
    log.info(f"{'=' * 60}")
    log.info(f"[Fabric] 生成面料图, 成衣: {len(data.image_base64) // 1024}KB")
    log.info(
        f"[Fabric] fabric_info: {json.dumps(data.fabric_info, ensure_ascii=False)}"
    )
    user = db.query(User).filter(User.username == current_username).first()
    runtime_ai_config = get_runtime_ai_config(user)
    effective_image_model = (
        data.image_edit_model
        or (user.ai_image_model if user else None)
        or settings.AI_IMAGE_EDIT_MODEL
    )
    if not has_ai_config(runtime_ai_config) or not effective_image_model:
        raise HTTPException(400, "请先在 API 配置页填写转发 Key，或联系管理员配置默认 AI_API_KEY")

    try:
        fi = data.fabric_info
        prompt = (
            f"Please generate a high-resolution flat fabric pattern image based on this garment photo.\n"
            f"\nRequirements:\n"
            f"1. Output a RECTANGULAR flat fabric image (not garment shape, just the fabric laid flat)\n"
            f"2. Fabric type: {fi.get('fabric_type', 'digital print')}\n"
            f"3. Pattern elements: {fi.get('pattern_elements', 'floral')}\n"
            f"4. Pattern direction: {fi.get('pattern_direction', 'uniform')}\n"
            f"5. Color transition: {fi.get('color_transition', 'none')}\n"
            f"6. Pattern density: {fi.get('density', 'medium')}\n"
            f"7. Remove all garment wrinkles/shadows - show flat fabric only\n"
            f"8. The pattern must be clear, colors accurate, suitable for garment piece overlay\n"
            f"9. Aspect ratio: 3:4\n"
            f"10. The image should look like a piece of fabric photographed from directly above on a flat surface"
        )

        log.info(f"[Fabric] 提示词: {prompt[:200]}")

        result_url, desc = call_image_model(
            images_b64=[data.image_base64],
            prompt=prompt,
            model_override=effective_image_model,
            runtime_config=runtime_ai_config,
        )

        log.info(f"[Fabric] 面料图生成完成")
        return {"code": 200, "data": {"fabric_url": result_url, "description": desc}}

    except Exception as e:
        log.error(f"面料图生成失败: {e}", exc_info=True)
        raise HTTPException(500, f"面料图生成失败: {str(e)}")


@router.post("/ai/generate-design-images")
async def generate_design_images(
    data: GenerateDesignImagesRequest,
    current_username: str = Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """通用豆包出图：支持纯文生图和基于参考图的连续组图。"""
    log.info(f"{'=' * 60}")
    log.info(
        f"[DesignImage] 通用出图, 参考图={'是' if data.reference_image_base64 else '否'}, count={data.count}, size={data.size or '2K'}"
    )
    user = db.query(User).filter(User.username == current_username).first()
    runtime_ai_config = get_runtime_ai_config(user)
    effective_image_model = (
        data.image_edit_model
        or (user.ai_image_model if user else None)
        or settings.AI_IMAGE_EDIT_MODEL
    )
    if not has_ai_config(runtime_ai_config) or not effective_image_model:
        raise HTTPException(400, "请先在 API 配置页填写转发 Key，或联系管理员配置默认 AI_API_KEY")

    prompt = (data.prompt or "").strip()
    if not prompt:
        raise HTTPException(400, "prompt 不能为空")

    try:
        count = max(1, min(int(data.count or 1), 4))
        images_b64 = [data.reference_image_base64] if data.reference_image_base64 else []
        result = call_image_model_batch(
            images_b64=images_b64,
            prompt=prompt,
            model_override=effective_image_model,
            size=data.size or "2K",
            max_images=count,
            stream=(count > 1),
            runtime_config=runtime_ai_config,
        )
        images = result.get("images", [])
        return {
            "code": 200,
            "data": {
                "images": images,
                "image_urls": [item["url"] for item in images],
                "count": len(images),
                "used_reference_image": bool(data.reference_image_base64),
                "usage": result.get("usage"),
            },
        }
    except Exception as e:
        log.error(f"通用出图失败: {e}", exc_info=True)
        err_msg = str(e)
        if "data_inspection_failed" in err_msg or "inappropriate" in err_msg:
            raise HTTPException(400, "图片内容未通过平台安全审核，请更换提示词或参考图")
        raise HTTPException(500, f"通用出图失败: {err_msg}")


@router.post("/ai/crop-piece")
async def crop_piece(
    data: CropPieceRequest, current_username: str = Depends(get_current_user)
):
    """从预览图中按坐标裁切指定裁片区域（Pillow，像素级精准）"""
    log.info(f"{'=' * 60}")
    log.info(f"[CropPiece] 裁切: {data.piece_name}")
    log.info(
        f"[CropPiece] 画布: {data.canvas_width}x{data.canvas_height}, 裁片区域: ({data.piece_left},{data.piece_top}) {data.piece_width}x{data.piece_height}"
    )

    try:
        result_b64 = _crop_piece_from_preview(data)
        return {
            "code": 200,
            "data": {"cropped_base64": result_b64, "piece_name": data.piece_name},
        }
    except Exception as e:
        log.error(f"裁切失败: {e}", exc_info=True)
        raise HTTPException(500, f"裁切失败: {str(e)}")


@router.post("/ai/refine-piece")
async def refine_piece(
    data: RefinePieceRequest,
    current_username: str = Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """AI 细化裁切图：根据图案类型生成不同提示词"""
    log.info(f"{'=' * 60}")
    log.info(
        f"[RefinePiece] 细化: {data.piece_name}, 类型: {data.pattern_type}, 比例: {data.aspect_ratio}"
    )
    log.info(
        f"[RefinePiece] 裁片尺寸: {data.piece_width}x{data.piece_height}, 图片: {len(data.cropped_base64) // 1024}KB"
    )

    user = db.query(User).filter(User.username == current_username).first()
    runtime_ai_config = get_runtime_ai_config(user)
    effective_image_model = (
        data.image_edit_model
        or (user.ai_image_model if user else None)
        or settings.AI_IMAGE_EDIT_MODEL
    )
    if not has_ai_config(runtime_ai_config) or not effective_image_model:
        raise HTTPException(400, "请先在 API 配置页填写转发 Key，或联系管理员配置默认 AI_API_KEY")

    try:
        size_hint = ""
        if data.aspect_ratio:
            size_hint = f"输出图片宽高比必须为 {data.aspect_ratio}。"
        if data.piece_width and data.piece_height:
            size_hint += f"输出尺寸至少 {int(data.piece_width * 1.15)}x{int(data.piece_height * 1.15)} 像素。"

        if data.pattern_type == "fill_pattern":
            prompt = data.prompt or (
                f"这是从服装裁片中裁切出来的花型图案区域。"
                f"请将它处理为一张干净的矩形花型图："
                f"去掉所有轮廓线、标签文字和边缘空白；"
                f"花型图案要完整铺满整个矩形，无空白边距；"
                f"向四周自然延展重复纹样，保持花型连续无接缝；"
                f"保持花样的颜色和细节清晰。{size_hint}"
            )
        elif data.pattern_type == "theme_pattern":
            prompt = data.prompt or (
                f"这是从服装裁片中裁切出来的主题图案区域（如卡通人物、Logo、印花）。"
                f"请提取其中的主题图案，输出为纯白色背景(#FFFFFF)上的主题图案："
                f"主题图案要完整清晰，保持原始颜色和细节；"
                f"背景必须是纯白色(#FFFFFF)，不要任何其他底色或纹理；"
                f"去掉轮廓线和标签文字；主题图案在画面中居中放置。{size_hint}"
            )
        elif data.pattern_type == "mixed_fill":
            prompt = data.prompt or (
                f"这是从服装裁片中裁切出来的区域，包含主题图案和花型底纹。"
                f"请只提取底部的花型/纹理图案，去掉上面的主题图案（人物/Logo）："
                f"用底纹自然填充主题图案原来的位置；"
                f"花型要完整铺满整个矩形，保持纹样连续；"
                f"去掉轮廓线和标签文字。{size_hint}"
            )
        elif data.pattern_type == "mixed_theme":
            prompt = data.prompt or (
                f"这是从服装裁片中裁切出来的区域，包含主题图案和花型底纹。"
                f"请只提取主题图案（人物/Logo/大面积印花），输出为纯白色背景(#FFFFFF)："
                f"主题图案要完整清晰，保持原始颜色和细节；"
                f"背景必须是纯白色(#FFFFFF)，去掉所有底纹；"
                f"去掉轮廓线和标签文字；主题图案在画面中居中放置。{size_hint}"
            )
        else:
            prompt = (
                data.prompt
                or f"细化这张图片，去掉轮廓线和标签文字，保持清晰。{size_hint}"
            )

        log.info(f"[RefinePiece] 提示词: {prompt[:200]}")

        result_url, desc = call_image_model(
            images_b64=[data.cropped_base64],
            prompt=prompt,
            model_override=effective_image_model,
            runtime_config=runtime_ai_config,
        )
        return {
            "code": 200,
            "data": {
                "refined_url": result_url,
                "piece_name": data.piece_name,
                "pattern_type": data.pattern_type,
            },
        }
    except Exception as e:
        log.error(f"细化失败: {e}", exc_info=True)
        err_msg = str(e)
        if "data_inspection_failed" in err_msg:
            raise HTTPException(400, "图片内容未通过安全审核")
        raise HTTPException(500, f"细化失败: {err_msg}")


@router.post("/ai/extract-piece-pattern")
async def extract_piece_pattern(
    data: ExtractPieceRequest,
    current_username: str = Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """阶段2：从预览图中提取指定裁片区域 → 输出矩形花样图"""
    log.info(f"{'=' * 60}")
    log.info(
        f"[ExtractPiece] 裁片: {data.piece_name}, 描述: '{data.piece_description}', 预览图: {len(data.preview_base64) // 1024}KB"
    )
    user = db.query(User).filter(User.username == current_username).first()
    runtime_ai_config = get_runtime_ai_config(user)
    effective_image_model = (
        getattr(data, "image_edit_model", None)
        or (user.ai_image_model if user else None)
        or settings.AI_IMAGE_EDIT_MODEL
    )
    if not has_ai_config(runtime_ai_config) or not effective_image_model:
        raise HTTPException(400, "请先在 API 配置页填写转发 Key，或联系管理员配置默认 AI_API_KEY")

    try:
        prompt = data.prompt or (
            f"这是一张服装裁片花样预览图，包含多个裁片。"
            f"请提取其中【{data.piece_name}】的花样区域"
            f"{'（' + data.piece_description + '）' if data.piece_description else ''}，"
            f"将该区域的花样输出为一张完整的矩形图片。"
            f"要求：只保留花样内容，去掉轮廓线，填满整个矩形，保持清晰。"
        )
        result_url, desc = call_image_model(
            images_b64=[data.preview_base64],
            prompt=prompt,
            model_override=effective_image_model,
            runtime_config=runtime_ai_config,
        )
        return {
            "code": 200,
            "data": {
                "pattern_url": result_url,
                "piece_name": data.piece_name,
                "description": desc,
            },
        }
    except Exception as e:
        log.error(f"裁片提取失败: {e}", exc_info=True)
        raise HTTPException(500, f"裁片提取失败: {str(e)}")


# ==================== 工具函数 ====================


def _crop_piece_from_preview(data: CropPieceRequest) -> str:
    """用 Pillow 从预览图中按 PS 坐标裁切指定区域（带出血线）"""
    from PIL import Image
    import io

    img_bytes = base64.b64decode(data.preview_base64)
    preview_img = Image.open(io.BytesIO(img_bytes))
    pw, ph = preview_img.size

    scale_x = pw / data.canvas_width
    scale_y = ph / data.canvas_height

    bleed_x = data.piece_width * data.padding
    bleed_y = data.piece_height * data.padding

    left = max(0, (data.piece_left - bleed_x) * scale_x)
    top = max(0, (data.piece_top - bleed_y) * scale_y)
    right = min(pw, (data.piece_left + data.piece_width + bleed_x) * scale_x)
    bottom = min(ph, (data.piece_top + data.piece_height + bleed_y) * scale_y)

    log.info(f"[CropPiece] PS 画布: {data.canvas_width}x{data.canvas_height}")
    log.info(f"[CropPiece] 预览图: {pw}x{ph}")
    log.info(f"[CropPiece] 缩放比: x={scale_x:.4f}, y={scale_y:.4f}")
    log.info(
        f"[CropPiece] PS 裁片区域: ({data.piece_left},{data.piece_top}) {data.piece_width}x{data.piece_height}"
    )
    log.info(
        f"[CropPiece] 出血线: {data.padding * 100:.0f}% → x±{bleed_x:.0f}px, y±{bleed_y:.0f}px"
    )
    log.info(
        f"[CropPiece] 预览图裁切: ({left:.0f},{top:.0f})-({right:.0f},{bottom:.0f}) = {right - left:.0f}x{bottom - top:.0f}px"
    )

    cropped = preview_img.crop((int(left), int(top), int(right), int(bottom)))

    import os, time

    debug_dir = os.path.join(
        os.path.dirname(__file__), "..", "..", "..", "debug_images"
    )
    os.makedirs(debug_dir, exist_ok=True)
    ts = int(time.time())
    debug_path = os.path.join(debug_dir, f"{ts}_crop_{data.piece_name}.png")
    cropped.save(debug_path)
    log.info(f"[CropPiece] 裁切结果已保存: {debug_path}")

    buf = io.BytesIO()
    cropped.save(buf, format="PNG")
    return base64.b64encode(buf.getvalue()).decode()
