"""
LLM 故事生成服务 — 基于火山引擎豆包大模型
"""
import json
import logging
from django.conf import settings

try:
    from volcenginesdkarkruntime import Ark
    ARK_AVAILABLE = True
except ImportError:
    ARK_AVAILABLE = False

logger = logging.getLogger(__name__)

STORY_SYSTEM_PROMPT = """# 角色

你是「卡皮巴拉故事工坊」的首席故事大师。你为 3-8 岁的小朋友创作原创童话故事。

# 任务

根据用户提供的**角色、场景、道具**素材，创作一个完整的儿童故事。

# 输出格式

你 **必须** 只返回如下 JSON，不要返回任何其他内容（不要 markdown 代码块，不要解释）：

{"title": "故事标题（6字以内）", "content": "故事正文"}

# 故事创作规范

1. **字数**：正文 400-600 字，不要太短也不要太长
2. **段落**：用 `\\n\\n` 分段，每段 2-4 句话
3. **语言**：简单易懂，适合给小朋友朗读；可以包含拟声词（"哗啦啦"、"咕噜噜"）和语气词（"哇！"、"嘿嘿"）
4. **结构**：开头引入角色和场景 → 中间遇到挑战或趣事 → 结尾温馨圆满
5. **情感**：温暖、有趣、充满想象力，带一点小幽默
6. **教育**：自然融入一个小道理（勇气、友谊、分享等），不要说教
7. **创意**：即使收到相同的素材组合，每次也要创作全新的、不同的故事情节
8. **角色融合**：所有用户选择的角色、场景、道具都必须在故事中出现并发挥作用
9. **标题**：简短有趣，6 个字以内，能引起小朋友的好奇心"""


def build_user_prompt(characters, scenes, props):
    """构建用户提示词"""
    parts = []
    if characters:
        parts.append(f"角色：{', '.join(characters)}")
    if scenes:
        parts.append(f"场景：{', '.join(scenes)}")
    if props:
        parts.append(f"道具：{', '.join(props)}")
    return '请根据以下元素创作一个儿童故事：\n' + '\n'.join(parts)


def generate_story_stream(characters, scenes, props):
    """
    流式生成故事，yield SSE 事件字符串。
    使用火山引擎豆包大模型（Ark SDK）。

    Yields:
        str: SSE 格式的事件数据行
    """
    config = settings.LLM_CONFIG

    if not config.get('API_KEY'):
        yield _sse_event('error', {'message': 'Volcengine API Key 未配置'})
        return

    if not ARK_AVAILABLE:
        yield _sse_event('error', {'message': 'volcengine SDK 未安装，请运行 pip install "volcengine-python-sdk[ark]"'})
        return

    yield _sse_event('stage', {
        'stage': 'connecting',
        'progress': 0,
        'message': '正在收集灵感碎片...',
    })

    client = Ark(api_key=config['API_KEY'])

    user_prompt = build_user_prompt(characters, scenes, props)

    try:
        yield _sse_event('stage', {
            'stage': 'generating',
            'progress': 10,
            'message': '故事正在诞生...',
        })

        stream = client.chat.completions.create(
            model=config['MODEL_NAME'],
            messages=[
                {'role': 'system', 'content': STORY_SYSTEM_PROMPT},
                {'role': 'user', 'content': user_prompt},
            ],
            max_tokens=2048,
            stream=True,
        )

        full_content = ''
        chunk_count = 0

        for chunk in stream:
            delta = chunk.choices[0].delta if chunk.choices else None
            if delta and delta.content:
                full_content += delta.content
                chunk_count += 1

                if chunk_count % 5 == 0:
                    progress = min(10 + int(chunk_count * 0.5), 80)
                    yield _sse_event('stage', {
                        'stage': 'generating',
                        'progress': progress,
                        'message': '故事正在诞生...',
                    })

        yield _sse_event('stage', {
            'stage': 'parsing',
            'progress': 85,
            'message': '正在编制最后的魔法...',
        })

        result = _parse_story_json(full_content)

        # ── Generate cover image ──
        yield _sse_event('stage', {
            'stage': 'cover',
            'progress': 90,
            'message': '正在绘制故事封面...',
        })

        cover_url = ''
        try:
            cover_url = _generate_and_upload_cover(
                result['title'], result['content'], config
            )
        except Exception as cover_err:
            logger.warning(f'Cover generation failed (non-fatal): {cover_err}')

        yield _sse_event('done', {
            'stage': 'done',
            'progress': 100,
            'message': '大功告成！',
            'title': result['title'],
            'content': result['content'],
            'cover_url': cover_url,
        })

    except Exception as e:
        logger.error(f'LLM story generation failed: {e}')
        yield _sse_event('error', {'message': f'故事生成失败: {str(e)}'})


def _parse_story_json(text):
    """从 LLM 输出中解析故事 JSON"""
    text = text.strip()
    if text.startswith('```'):
        text = text.split('\n', 1)[1] if '\n' in text else text[3:]
    if text.endswith('```'):
        text = text[:-3]
    text = text.strip()

    try:
        data = json.loads(text)
        return {
            'title': data.get('title', '未命名故事'),
            'content': data.get('content', text),
        }
    except json.JSONDecodeError:
        return {
            'title': '新故事',
            'content': text,
        }


def _extract_image_description(title, content, client, model_name):
    """
    用 LLM 从故事内容中提炼 ≤50 字的画面描述：主体 + 场景 + 事件。
    返回纯文本描述字符串。
    """
    system = (
        "你是图像提示词专家。从给定的儿童故事中，提取主体、场景与核心事件，"
        "串联成一幅画的中文描述。要求：\n"
        "1. 不超过50个汉字\n"
        "2. 只输出描述本身，不加任何解释、前缀或多余标点\n"
        "3. 描述需具体生动，适合儿童绘本插画"
    )
    user = f"故事标题：{title}\n故事内容：{content[:800]}"
    resp = client.chat.completions.create(
        model=model_name,
        messages=[
            {'role': 'system', 'content': system},
            {'role': 'user', 'content': user},
        ],
        max_tokens=80,
        stream=False,
    )
    return resp.choices[0].message.content.strip()


def _generate_and_upload_cover(title, content, config):
    """
    使用豆包文生图模型生成故事封面，上传到 OSS 并返回 URL。
    失败时抛出异常（由调用方捕获，不影响主流程）。
    """
    import uuid
    import requests as req_lib
    from datetime import datetime
    from django.conf import settings
    from volcenginesdkarkruntime import Ark

    client = Ark(api_key=config['API_KEY'])

    # 用 LLM 从故事内容提炼 ≤50 字画面描述
    scene_desc = _extract_image_description(
        title, content, client, config['MODEL_NAME']
    )
    logger.info(f'Cover image description: {scene_desc}')

    image_prompt = f"儿童绘本封面插画，{scene_desc}，卡通可爱风格，色彩明亮鲜艳，高质量插画"

    image_model = config.get('IMAGE_MODEL_NAME', 'doubao-seedream-4-5-251128')
    image_size = config.get('IMAGE_SIZE', '2560x1440')

    result = client.images.generate(
        model=image_model,
        prompt=image_prompt,
        size=image_size,
        response_format='url',
        watermark=False,
    )

    image_url = result.data[0].url

    # Download from temporary URL and upload to OSS
    resp = req_lib.get(image_url, timeout=60)
    resp.raise_for_status()

    from utils.oss import get_oss_client
    oss_client = get_oss_client()
    key = f"stories/covers/{datetime.now().strftime('%Y%m%d')}/{uuid.uuid4().hex}.jpg"
    oss_client.bucket.put_object(
        key, resp.content,
        headers={'Content-Type': 'image/jpeg'},
    )

    oss_config = settings.ALIYUN_OSS
    if oss_config.get('CUSTOM_DOMAIN'):
        return f"https://{oss_config['CUSTOM_DOMAIN']}/{key}"
    return f"https://{oss_config['BUCKET_NAME']}.{oss_config['ENDPOINT']}/{key}"


def _sse_event(event, data):
    """格式化 SSE 事件"""
    return f"event: {event}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n"
