"""DA-14: 전체 파이프라인 (5단계).

1. Kei 실장: 꼭지 추출 + 분석
2. 디자인 팀장: 레이아웃 설계
3. 텍스트 편집자: 텍스트 정리
4. 디자인 실무자: HTML 조립
5. 디자인 팀장: 전체 재검토
"""
from __future__ import annotations

import json
import logging
import re
from typing import Any, AsyncIterator

import anthropic

from src.kei_client import classify_content, manual_classify
from src.design_director import create_layout_concept, LAYOUT_PRESETS, select_preset
from src.content_editor import fill_content
from src.renderer import render_slide
from src.image_utils import get_image_sizes, embed_images
from src.config import settings

logger = logging.getLogger(__name__)


async def generate_slide(
    content: str,
    manual_layout: dict[str, Any] | None = None,
    base_path: str = "",
) -> AsyncIterator[dict[str, str]]:
    """콘텐츠를 슬라이드 HTML로 변환하는 5단계 파이프라인.

    Yields:
        SSE 이벤트: progress / result / error
    """
    try:
        # 1단계: Kei 실장 — 꼭지 추출 + 분석
        yield {"event": "progress", "data": "1/5 Kei 실장이 꼭지를 추출 중..."}

        if manual_layout:
            analysis = manual_layout
        else:
            analysis = await classify_content(content)
            if analysis is None:
                analysis = manual_classify(content)

        topic_count = len(analysis.get("topics", []))
        page_count = analysis.get("total_pages", 1)
        logger.info(f"1단계 완료: {topic_count}개 꼭지, {page_count}페이지")

        # 이미지 크기 측정 (base_path 있을 때만)
        image_sizes = get_image_sizes(content, base_path)
        if image_sizes:
            analysis["image_sizes"] = image_sizes
            logger.info(f"이미지 측정: {len(image_sizes)}개")

        # 2단계: 디자인 팀장 — Step A(프리셋) + Step B(블록 매핑)
        yield {"event": "progress", "data": "2/5 디자인 팀장이 레이아웃을 설계 중..."}

        layout_concept = await create_layout_concept(content, analysis)

        total_blocks = sum(
            len(p.get("blocks", [])) for p in layout_concept.get("pages", [])
        )
        logger.info(
            f"2단계 완료: {len(layout_concept.get('pages', []))}페이지, "
            f"{total_blocks}개 블록"
        )

        # 3단계: 텍스트 편집자 — 텍스트 정리
        yield {"event": "progress", "data": "3/5 텍스트 편집자가 핵심을 정리 중..."}

        layout_concept = await fill_content(content, layout_concept, analysis)
        logger.info("3단계 완료: 텍스트 정리")

        # 4단계: 디자인 실무자 — 디자인 조정 + HTML 조립
        yield {"event": "progress", "data": "4/5 디자인 실무자가 슬라이드를 조립 중..."}

        layout_concept = await _adjust_design(layout_concept, analysis)
        html = render_slide(layout_concept)
        logger.info("4단계 완료: HTML 조립")

        # 5단계: 디자인 팀장 — 전체 재검토 (최대 MAX_REVIEW_ROUNDS회)
        MAX_REVIEW_ROUNDS = 2  # 무한 루프 방지 — 최대 재조정 횟수
        yield {"event": "progress", "data": "5/5 디자인 팀장이 전체 균형을 검토 중..."}

        for review_round in range(MAX_REVIEW_ROUNDS):
            review_result = await _review_balance(html, layout_concept, content)

            if not review_result or not review_result.get("needs_adjustment"):
                if review_round == 0:
                    logger.info("5단계 완료: 조정 불필요")
                else:
                    logger.info(f"5단계 완료: {review_round}차 조정 후 균형 확인")
                break

            issues = review_result.get("issues", [])
            logger.info(
                f"5단계 ({review_round + 1}/{MAX_REVIEW_ROUNDS}): "
                f"조정 필요 — {issues}"
            )

            layout_concept = await _apply_adjustments(
                layout_concept, review_result, content
            )
            html = render_slide(layout_concept)
            logger.info(f"5단계: {review_round + 1}차 조정 반영, 재검토 진행")
        else:
            # MAX_REVIEW_ROUNDS 초과
            logger.warning(
                f"5단계: 최대 재조정 횟수({MAX_REVIEW_ROUNDS}) 도달. 현재 결과로 확정."
            )

        # D-5: 이미지를 base64로 삽입 (다운로드 HTML에서도 보이도록)
        if base_path:
            html = embed_images(html, base_path)
            logger.info("이미지 base64 삽입 완료")

        yield {"event": "result", "data": html}
        logger.info(f"슬라이드 생성 완료: {len(layout_concept.get('pages', []))}페이지")

    except Exception as e:
        logger.exception(f"파이프라인 오류: {e}")
        yield {"event": "error", "data": str(e)}


async def _adjust_design(
    layout_concept: dict[str, Any],
    analysis: dict[str, Any],
) -> dict[str, Any]:
    """4단계 전반: 디자인 실무자가 텍스트 양에 맞게 CSS를 조정한다.

    각 area별 블록 수, 텍스트 총량, zone 예산을 계산하고,
    Sonnet이 area별 CSS 변수 override를 결정한다.
    블록 템플릿이 이미 CSS 변수(var(--font-body) 등)를 사용하므로,
    area div에서 변수를 override하면 내부 블록이 자동 조정된다.
    """
    try:
        client = anthropic.AsyncAnthropic(api_key=settings.anthropic_api_key)

        # 프리셋 정보 가져오기
        preset_name = select_preset(analysis)
        preset = LAYOUT_PRESETS.get(preset_name, {})
        zones = preset.get("zones", {})

        for page in layout_concept.get("pages", []):
            # area별 블록 수 + 텍스트 총량 집계
            area_info = {}
            for block in page.get("blocks", []):
                area = block.get("area", "body")
                if area not in area_info:
                    zone = zones.get(area, {})
                    area_info[area] = {
                        "block_count": 0,
                        "total_chars": 0,
                        "budget_px": zone.get("budget_px", 490),
                        "width_pct": zone.get("width_pct", 100),
                        "block_types": [],
                    }
                data = block.get("data", {})
                text_len = len(json.dumps(data, ensure_ascii=False))
                area_info[area]["block_count"] += 1
                area_info[area]["total_chars"] += text_len
                area_info[area]["block_types"].append(block.get("type", ""))

            # area 정보 텍스트 구성
            area_lines = []
            for area_name, info in area_info.items():
                area_lines.append(
                    f"- {area_name} (예산 {info['budget_px']}px, 너비 {info['width_pct']}%): "
                    f"{info['block_count']}개 블록, 총 {info['total_chars']}자\n"
                    f"  블록 타입: {', '.join(info['block_types'])}"
                )

            system = (
                "당신은 디자인 실무자이다. 편집자가 정리한 텍스트가 각 영역에 잘 들어가도록 CSS를 조정한다.\n\n"
                "## 원칙\n"
                "- 텍스트를 자르지 않는다. 디자인이 텍스트에 맞춘다.\n"
                "- 빈 공간을 방치하지 않는다.\n"
                "- 텍스트가 많으면: 폰트/여백을 줄여서 맞춘다.\n"
                "- 텍스트가 적으면: 폰트/여백을 늘려서 채운다.\n\n"
                "## 조정 가능한 CSS 변수\n"
                "- --font-body (기본 0.95rem): 본문 폰트 크기\n"
                "- --font-subtitle (기본 1.25rem): 소제목 폰트 크기\n"
                "- --font-caption (기본 0.8rem): 캡션 폰트 크기\n"
                "- --spacing-inner (기본 16px): 블록 내부 여백\n"
                "- --spacing-block (기본 20px): 블록 간 간격\n"
                "- --spacing-small (기본 8px): 작은 여백\n\n"
                "## 출력 형식 (JSON만. 설명 없이.)\n"
                "각 area에 적용할 CSS 변수 override를 inline style 문자열로 반환.\n"
                "조정 불필요한 area는 빈 문자열.\n"
                '{"area_styles": {"body": "--font-body: 0.85rem; --spacing-inner: 10px;", "sidebar": "", "footer": ""}}'
            )

            user_prompt = (
                f"## 각 영역 현황\n" + "\n".join(area_lines) +
                f"\n\n위 영역별로 CSS 변수 조정이 필요한지 판단하여 JSON으로 반환해줘."
            )

            response = await client.messages.create(
                model="claude-sonnet-4-20250514",
                max_tokens=1024,
                system=system,
                messages=[{"role": "user", "content": user_prompt}],
            )

            result_text = response.content[0].text
            result = _parse_json(result_text)

            if result and "area_styles" in result:
                page["area_styles"] = result["area_styles"]
                logger.info(
                    f"디자인 조정: {', '.join(f'{k}={bool(v)}' for k, v in result['area_styles'].items())}"
                )
            else:
                page["area_styles"] = {}
                logger.info("디자인 조정: 조정 불필요 또는 파싱 실패")

    except Exception as e:
        logger.warning(f"디자인 조정 실패 (기존 스타일로 렌더링): {e}")
        # 실패 시 area_styles 없음 → 기존과 동일하게 렌더링
        for page in layout_concept.get("pages", []):
            if "area_styles" not in page:
                page["area_styles"] = {}

    return layout_concept


async def _review_balance(
    html: str,
    layout_concept: dict[str, Any],
    content: str,
) -> dict[str, Any] | None:
    """5단계: 디자인 팀장이 1차 조립 결과를 재검토한다.

    HTML 코드 기반으로 구조적 점검:
    - 빈 블록 감지
    - 블록 간 채움 비율 불균형
    - 이미지/표 크기 적절성
    """
    try:
        client = anthropic.AsyncAnthropic(api_key=settings.anthropic_api_key)

        # 블록별 텍스트 양 요약
        block_summary = []
        for page in layout_concept.get("pages", []):
            for block in page.get("blocks", []):
                data = block.get("data", {})
                text_len = len(json.dumps(data, ensure_ascii=False))
                block_summary.append(
                    f"  {block.get('area')}/{block.get('type')}: "
                    f"데이터 {text_len}자"
                )

        system = (
            "당신은 디자인 팀장이다. 1차 조립 결과(HTML)를 검토하여 균형을 점검한다.\n\n"
            "## 점검 항목\n"
            "1. 빈 블록: 데이터가 없거나 극히 적은 블록\n"
            "2. 채움 불균형: 한 블록은 빽빽하고 다른 블록은 비어있음\n"
            "3. 이미지/표: 너무 작거나 큰 것은 없는지\n"
            "4. 전체 정보량: 한 페이지에 너무 많거나 적은지\n"
            "5. HTML 구조: 블록이 영역 안에 잘 배치되었는지\n\n"
            "## 조정 action 설명\n"
            "- expand: 텍스트를 늘린다. target_ratio로 얼마나 늘릴지 지정 (예: 1.3 = 30% 증가)\n"
            "- shrink: 텍스트를 줄인다. target_ratio로 얼마나 줄일지 지정 (예: 0.7 = 30% 감소)\n"
            "- rewrite: 텍스트를 완전히 재작성한다. detail에 재작성 방향 명시.\n\n"
            "## 출력 형식 (JSON만)\n"
            '{"needs_adjustment": true/false, '
            '"issues": ["이슈1", "이슈2"], '
            '"adjustments": [{"block_area": "...", "action": "expand|shrink|rewrite", '
            '"target_ratio": 1.3, "detail": "..."}]}'
        )

        user_prompt = (
            f"## 1차 조립 HTML\n{html}\n\n"
            f"## 블록별 데이터 양\n" + "\n".join(block_summary) +
            f"\n\n## 레이아웃 구조\n"
            f"페이지 수: {len(layout_concept.get('pages', []))}\n"
            f"총 블록 수: {sum(len(p.get('blocks', [])) for p in layout_concept.get('pages', []))}\n\n"
            f"위 HTML과 데이터를 보고 조정이 필요한지 판단해. JSON으로 답해."
        )

        response = await client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            system=system,
            messages=[{"role": "user", "content": user_prompt}],
        )

        result_text = response.content[0].text
        return _parse_json(result_text)

    except Exception as e:
        logger.warning(f"재검토 실패: {e}")
        return None


async def _apply_adjustments(
    layout_concept: dict[str, Any],
    review: dict[str, Any],
    content: str,
) -> dict[str, Any]:
    """재검토 결과에 따라 텍스트를 재편집한다."""
    adjustments = review.get("adjustments", [])
    if not adjustments:
        return layout_concept

    # 조정이 필요한 블록만 재편집
    for adj in adjustments:
        area = adj.get("block_area", "")
        action = adj.get("action", "")
        ratio = adj.get("target_ratio")
        detail = adj.get("detail", "")

        for page in layout_concept.get("pages", []):
            for block in page.get("blocks", []):
                if block.get("area") != area:
                    continue

                if action == "expand" and ratio:
                    for key in block.get("char_guide", {}):
                        block["char_guide"][key] = int(
                            block["char_guide"][key] * ratio
                        )
                    logger.info(f"조정: {area} → expand ×{ratio} ({detail})")

                elif action == "shrink" and ratio:
                    for key in block.get("char_guide", {}):
                        block["char_guide"][key] = int(
                            block["char_guide"][key] * ratio
                        )
                    logger.info(f"조정: {area} → shrink ×{ratio} ({detail})")

                elif action == "rewrite":
                    if "data" in block:
                        del block["data"]
                    block["reason"] = f"재작성: {detail}"
                    logger.info(f"조정: {area} → rewrite ({detail})")

    # 조정된 가이드로 재편집
    layout_concept = await fill_content(content, layout_concept)
    return layout_concept


def _parse_json(text: str) -> dict[str, Any] | None:
    """텍스트에서 JSON을 추출한다."""
    patterns = [
        r"```json\s*(.*?)```",
        r"```\s*(.*?)```",
        r"(\{.*\})",
    ]
    for pattern in patterns:
        match = re.search(pattern, text, re.DOTALL)
        if match:
            try:
                return json.loads(match.group(1).strip())
            except json.JSONDecodeError:
                continue
    return None
