"""
Fanfic Writer v2.0 - Writing Loop (Phase 6)
Core writing pipeline with QC, Attempt cycle, and FORCED handling
"""
import json
import re
from pathlib import Path
from typing import Dict, Any, Optional, List, Tuple, Callable
from dataclasses import dataclass
from enum import Enum

from .config_manager import ConfigManager
from .state_manager import StateManager, Evidence
from .prompt_assembly import PromptBuilder
from .price_table import PriceTableManager
from .atomic_io import atomic_write_text, atomic_write_json, atomic_append_jsonl
from .utils import get_timestamp_iso, generate_event_id, sanitize_chapter_filename


class QCStatus(Enum):
    INIT = "INIT"
    PASS = "PASS"
    WARNING = "WARNING"
    REVISE = "REVISE"
    FORCED = "FORCED"


@dataclass
class QCResult:
    """Quality Check result"""
    score: int
    status: QCStatus
    dimensions: Dict[str, int]
    pros: List[str]
    cons: List[str]
    rewrite_plan: str
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            'score': self.score,
            'status': self.status.value,
            'dimensions': self.dimensions,
            'pros': self.pros,
            'cons': self.cons,
            'rewrite_plan': self.rewrite_plan
        }


class WritingLoop:
    """
    Phase 6: Chapter-by-chapter writing with quality control
    
    Flow:
    6.1 Sanitizer -> 6.2 Outline Confirm -> 6.3 Draft -> 6.4 QC -> 6.5 Content Confirm -> 6.6 State Commit
    """
    
    def __init__(
        self,
        run_dir: Path,
        model_callable: Callable,
        config_manager: Optional[ConfigManager] = None,
        state_manager: Optional[StateManager] = None,
        prompt_builder: Optional[PromptBuilder] = None
    ):
        self.run_dir = Path(run_dir)
        self.model_callable = model_callable
        
        self.config = (config_manager or ConfigManager(run_dir)).load()
        self.state = state_manager or StateManager(run_dir)
        self.prompt_builder = prompt_builder
        self.price_mgr = PriceTableManager(run_dir)
        
        # QC config
        qc_config = self.config.get('qc', {})
        self.pass_threshold = qc_config.get('pass_threshold', 85)
        self.warning_threshold = qc_config.get('warning_threshold', 75)
        self.max_attempts = self.config.get('generation', {}).get('max_attempts', 3)
        self.mode = self.config.get('generation', {}).get('mode', 'manual')
        
        # Ending state check
        self.target_words = self.config.get('book', {}).get('target_word_count', 100000)
        self.max_chapters = 200  # Hard limit
    
    def _get_forced_streak(self) -> int:
        """Get current forced_streak value"""
        state_path = self.run_dir / "4-state" / "4-writing-state.json"
        try:
            with open(state_path, 'r', encoding='utf-8') as f:
                state = json.load(f)
            return state.get('forced_streak', 0)
        except:
            return 0
    
    # ========================================================================
    # 6.1 Sanitizer
    # ========================================================================
    
    def sanitizer(self, chapter_num: int) -> Dict[str, Any]:
        """
        6.1: State Sanitizer
        
        Reads state panels and extracts invariants for chapter generation
        """
        print(f"[6.1] Sanitizer for Chapter {chapter_num}")
        
        # Get invariants
        invariants = self.state.get_invariants(chapter_num)
        
        # Check for open backpatch issues
        backpatch_path = self.run_dir / "4-state" / "backpatch.jsonl"
        open_issues = []
        if backpatch_path.exists():
            with open(backpatch_path, 'r', encoding='utf-8') as f:
                for line in f:
                    try:
                        issue = json.loads(line.strip())
                        if issue.get('status') == 'open':
                            open_issues.append(issue)
                    except:
                        pass
        
        # Build sanitized context
        context_parts = ["## 不变量 (Invariants) - 必须延续"]
        
        for char_name, fields in invariants.get('characters', {}).items():
            context_parts.append(f"\n### {char_name}")
            for field, value in fields.items():
                context_parts.append(f"- {field}: {value}")
        
        if open_issues:
            context_parts.append("\n## Open Backpatch Issues")
            for issue in open_issues[:3]:  # Top 3
                context_parts.append(f"- 第{issue.get('chapter')}章: {issue.get('issue', '')}")
        
        sanitized_context = "\n".join(context_parts)
        
        # Log sanitizer output
        sanitizer_record = {
            'timestamp': get_timestamp_iso(),
            'chapter': chapter_num,
            'invariants_enforced': list(invariants.get('characters', {}).keys()),
            'soft_retcons_applied': [],
            'reason': '提取不变量用于第{chapter_num}章生成',
            'sanitized_context': sanitized_context[:500]  # Truncated
        }
        
        atomic_append_jsonl(
            self.run_dir / "4-state" / "sanitizer_output.jsonl",
            sanitizer_record
        )
        
        print(f"[6.1] Complete: {len(invariants.get('characters', {}))} characters")
        return {
            'invariants': invariants,
            'sanitized_context': sanitized_context,
            'open_backpatch_issues': open_issues
        }
    
    # ========================================================================
    # 6.2 Outline Generation & Confirm
    # ========================================================================
    
    def generate_chapter_outline(
        self,
        chapter_num: int,
        previous_content: str = ""
    ) -> str:
        """6.2: Generate detailed chapter outline"""
        print(f"[6.2] Generate outline for Chapter {chapter_num}")
        
        # Load chapter summary from plan
        plan_path = self.run_dir / "2-planning" / "2-chapter-plan.json"
        chapter_summary = "(暂无概要)"
        chapter_title = f"第{chapter_num}章"
        
        if plan_path.exists():
            with open(plan_path, 'r', encoding='utf-8') as f:
                plan = json.load(f)
            for ch in plan.get('chapters', []):
                if ch.get('chapter_number') == chapter_num:
                    chapter_summary = ch.get('summary', chapter_summary)
                    chapter_title = ch.get('title', chapter_title)
                    break
        
        target_words = self.config['book']['chapter_target_words']
        
        # Build prompt
        if self.prompt_builder:
            prompt, log_path = self.prompt_builder.build_chapter_outline_prompt(
                run_id=self.config['run_id'],
                chapter_num=chapter_num,
                chapter_title=chapter_title,
                chapter_summary=chapter_summary,
                previous_content=previous_content,
                target_words=target_words
            )
        else:
            # Simple fallback
            prompt = f"生成第{chapter_num}章详细大纲"
        
        # Call model (placeholder)
        outline = self.model_callable(prompt)
        
        # Save to drafts
        outline_path = self.run_dir / "drafts" / "outlines" / f"Ch{chapter_num:03d}_outline_attempt1.md"
        atomic_write_text(outline_path, outline)
        
        print(f"[6.2] Complete: outline saved")
        return outline
    
    # ========================================================================
    # 6.3 Draft Generation
    # ========================================================================
    
    def generate_draft(
        self,
        chapter_num: int,
        outline: str,
        previous_content: str = "",
        attempt: int = 1
    ) -> str:
        """6.3: Generate chapter draft"""
        print(f"[6.3] Generate draft for Chapter {chapter_num} (Attempt {attempt})")
        
        # Split outline into segments if needed
        # For now, generate as one piece
        target_words = self.config['book']['chapter_target_words']
        
        # Build prompt
        if self.prompt_builder:
            prompt, log_path = self.prompt_builder.build_chapter_draft_prompt(
                run_id=self.config['run_id'],
                chapter_num=chapter_num,
                chapter_title=f"第{chapter_num}章",
                detailed_outline=outline,
                previous_content=previous_content,
                segment_summary="本章全部内容",
                segment_words=target_words,
                is_first_segment=True,
                event_id=generate_event_id(self.config['run_id'], '6.3', chapter_num)
            )
        else:
            prompt = f"根据大纲生成第{chapter_num}章正文"
        
        # Call model
        draft = self.model_callable(prompt)
        
        # Save to drafts
        draft_path = self.run_dir / "drafts" / "chapters" / f"Ch{chapter_num:03d}_draft_attempt{attempt}.md"
        atomic_write_text(draft_path, draft)
        
        print(f"[6.3] Complete: {len(draft)} chars")
        return draft
    
    # ========================================================================
    # 6.4 Quality Check
    # ========================================================================
    
    def qc_evaluate(
        self,
        chapter_num: int,
        draft: str,
        outline: str,
        previous_content: str = ""
    ) -> QCResult:
        """
        6.4: Quality Check with multi-perspective review
        """
        print(f"[6.4] QC for Chapter {chapter_num}")
        
        # In real implementation, would call 3 critic models
        # For now, placeholder with simple heuristic
        
        # Simple QC: check word count, basic format
        word_count = len(draft)
        target = self.config['book']['chapter_target_words']
        
        # Calculate base score
        score = 80  # Start neutral
        
        # Word count check (+/- 10% is fine)
        if abs(word_count - target) / target > 0.1:
            score -= 5
        
        # Check for outline elements
        if outline[:100] in draft or any(line[:30] in draft for line in outline.split('\n')[:5]):
            score -= 10  # Outline leaked into draft
        
        # Multi-perspective simulation
        perspectives = ['editor', 'logic', 'continuity']
        scores = []
        
        for perspective in perspectives:
            # Would call actual critic model here
            perspective_score = score + (5 if perspective == 'editor' else 0)
            scores.append(perspective_score)
        
        final_score = int(sum(scores) / len(scores))
        
        # Determine status
        if final_score >= self.pass_threshold:
            status = QCStatus.PASS
        elif final_score >= self.warning_threshold:
            status = QCStatus.WARNING
        else:
            status = QCStatus.REVISE
        
        # Build result
        result = QCResult(
            score=final_score,
            status=status,
            dimensions={
                'outline_adherence': final_score - 5,
                'main_plot': final_score,
                'character': final_score - 2,
                'logic': final_score - 3,
                'continuity': final_score - 1,
                'pacing': final_score + 2,
                'style': final_score
            },
            pros=[
                "章节结构完整",
                "情节推进自然" if final_score > 75 else "情节有待加强",
                "人物行为符合设定" if final_score > 80 else "人物刻画需改进"
            ],
            cons=[] if status == QCStatus.PASS else [
                "建议加强细节描写",
                "部分对话可更口语化"
            ],
            rewrite_plan="" if status == QCStatus.PASS else "根据cons进行针对性修改"
        )
        
        # Save QC result
        qc_path = self.run_dir / "drafts" / "qc" / f"Ch{chapter_num:03d}_qc_attempt1.md"
        atomic_write_json(qc_path, result.to_dict())
        
        print(f"[6.4] Complete: score={final_score}, status={status.value}")
        return result
    
    # ========================================================================
    # Attempt Cycle
    # ========================================================================
    
    def attempt_cycle(
        self,
        chapter_num: int,
        outline: str,
        previous_content: str = ""
    ) -> Tuple[str, QCResult, int]:
        """
        Run Attempt 1-3 cycle
        
        Returns:
            (final_draft, qc_result, attempt_number)
        """
        attempt = 1
        best_draft = ""
        best_result = None
        
        while attempt <= self.max_attempts:
            print(f"\n[Attempt {attempt}/{self.max_attempts}]")
            
            # Generate draft
            draft = self.generate_draft(
                chapter_num, outline, previous_content, attempt
            )
            
            # QC
            result = self.qc_evaluate(chapter_num, draft, outline, previous_content)
            
            # Track best
            if best_result is None or result.score > best_result.score:
                best_draft = draft
                best_result = result
            
            # Check if passed
            if result.status in [QCStatus.PASS, QCStatus.WARNING]:
                print(f"[Attempt {attempt}] Passed with score {result.score}")
                return draft, result, attempt
            
            # Continue to next attempt
            if attempt < self.max_attempts:
                print(f"[Attempt {attempt}] Failed ({result.status.value}), retrying...")
                # In real implementation, would apply rewrite_plan for targeted refinement
            
            attempt += 1
        
        # All attempts exhausted -> FORCED
        print(f"[FORCED] All {self.max_attempts} attempts failed, best score: {best_result.score}")
        best_result.status = QCStatus.FORCED
        
        return best_draft, best_result, self.max_attempts
    
    # ========================================================================
    # 6.5 Save Chapter
    # ========================================================================
    
    def save_chapter(
        self,
        chapter_num: int,
        draft: str,
        qc_result: QCResult,
        chapter_title: str = ""
    ) -> Path:
        """6.5: Save chapter to chapters/ directory"""
        print(f"[6.5] Save Chapter {chapter_num}")
        
        if not chapter_title:
            chapter_title = f"第{chapter_num}章"
        
        # Determine filename based on QC status
        is_forced = qc_result.status == QCStatus.FORCED
        
        filename = sanitize_chapter_filename(chapter_num, chapter_title, is_forced)
        chapter_path = self.run_dir / "chapters" / filename
        
        # Add metadata header
        metadata = f"""[Chapter Metadata]
Number: {chapter_num}
Title: {chapter_title}
Word Count: {len(draft)}
QC Score: {qc_result.score}
QC Status: {qc_result.status.value}
Saved: {get_timestamp_iso()}
[End Metadata]

---

"""
        
        full_content = metadata + draft
        atomic_write_text(chapter_path, full_content)
        
        print(f"[6.5] Complete: {chapter_path}")
        return chapter_path
    
    # ========================================================================
    # 6.6 State Commit
    # ========================================================================
    
    def state_commit(
        self,
        chapter_num: int,
        draft: str,
        qc_result: QCResult,
        state_changes: Optional[Dict[str, Any]] = None
    ):
        """6.6: Commit state changes after chapter completion"""
        print(f"[6.6] State Commit for Chapter {chapter_num}")
        
        # Update writing state
        writing_state_path = self.run_dir / "4-state" / "4-writing-state.json"
        with open(writing_state_path, 'r', encoding='utf-8') as f:
            writing_state = json.load(f)
        
        writing_state['current_chapter'] = chapter_num
        writing_state['completed_chapters'].append(chapter_num)
        writing_state['qc_score'] = qc_result.score
        writing_state['qc_status'] = qc_result.status.value
        writing_state['updated_at'] = get_timestamp_iso()
        
        # Handle forced_streak
        if qc_result.status == QCStatus.FORCED:
            writing_state['forced_streak'] = writing_state.get('forced_streak', 0) + 1
            writing_state['flags']['prev_chapter_forced'] = True
            
            # Add to backpatch
            backpatch_record = {
                'id': generate_event_id(self.config['run_id'], 'BP', chapter_num),
                'chapter': chapter_num,
                'issue': f"QC score {qc_result.score} below threshold",
                'severity': 'high' if qc_result.score < 70 else 'medium',
                'evidence': f"QC result: {qc_result.cons}",
                'status': 'open',
                'created_at': get_timestamp_iso(),
                'closed_at': None,
                'fix_strategy': None,
                'qc_after_fix': None
            }
            atomic_append_jsonl(
                self.run_dir / "4-state" / "backpatch.jsonl",
                backpatch_record
            )
        else:
            writing_state['forced_streak'] = 0
            writing_state['flags']['prev_chapter_forced'] = False
        
        # Check forced_streak threshold
        if writing_state['forced_streak'] >= 2:
            writing_state['flags']['is_paused'] = True
            print("[ALERT] forced_streak >= 2, pausing for manual review")
        
        atomic_write_json(writing_state_path, writing_state)
        
        # Commit state changes if provided
        if state_changes:
            self.state.commit_chapter_state(chapter_num, state_changes)
        
        print(f"[6.6] Complete: forced_streak={writing_state['forced_streak']}")
    
    # ========================================================================
    # Write Single Chapter
    # ========================================================================
    
    def write_chapter(
        self,
        chapter_num: int,
        previous_content: str = "",
        auto_continue: bool = False
    ) -> Dict[str, Any]:
        """
        Write a single chapter through full 6.x pipeline
        
        Returns:
            Result dict with paths and status
        """
        print(f"\n{'='*50}")
        print(f"Writing Chapter {chapter_num}")
        print(f"{'='*50}\n")
        
        # 6.1 Sanitizer
        sanitizer_result = self.sanitizer(chapter_num)
        
        # 6.2 Generate Outline
        outline = self.generate_chapter_outline(chapter_num, previous_content)
        
        # In manual mode, would wait for user confirmation here
        if self.mode == 'manual' and not auto_continue:
            print("[Manual Mode] Waiting for outline confirmation...")
            # Would pause here in real implementation
        
        # 6.3-6.4 Attempt Cycle
        draft, qc_result, attempt_num = self.attempt_cycle(
            chapter_num, outline, previous_content
        )
        
        # 6.5 Save Chapter
        chapter_path = self.save_chapter(chapter_num, draft, qc_result)
        
        # 6.6 State Commit
        self.state_commit(chapter_num, draft, qc_result)
        
        return {
            'chapter_num': chapter_num,
            'chapter_path': chapter_path,
            'qc_score': qc_result.score,
            'qc_status': qc_result.status.value,
            'attempt': attempt_num,
            'forced_streak': self._get_forced_streak()
        }


# ============================================================================
# Module Test
# ============================================================================

if __name__ == "__main__":
    import tempfile
    
    print("=== Writing Loop Test (Phase 6) ===\n")
    
    # Mock model callable
    def mock_model(prompt: str) -> str:
        return "这是生成的内容...\n（模拟模型输出）\n字数填充：" + "内容" * 100
    
    with tempfile.TemporaryDirectory() as tmpdir:
        # Setup
        from .phase_runner import PhaseRunner, WorkspaceManager
        
        workspace = WorkspaceManager(Path(tmpdir) / "novels")
        runner = PhaseRunner(workspace)
        
        # Run phases 1-5
        results = runner.run_all(
            book_title="测试小说",
            genre="都市异能",
            target_words=50000,
            mode="auto"
        )
        
        run_dir = results['run_dir']
        
        # Create writing loop
        loop = WritingLoop(
            run_dir=run_dir,
            model_callable=mock_model
        )
        
        # Write chapter 1
        result = loop.write_chapter(1)
        
        print(f"\n[Test] Chapter 1 result:")
        print(f"  Status: {result['qc_status']}")
        print(f"  Score: {result['qc_score']}")
        print(f"  Attempt: {result['attempt']}")
        print(f"  Path: {result['chapter_path'].exists()}")
        
    print("\n=== All tests completed ===")
