"""
TiniX Story API Server
FastAPI wrapper cho toàn bộ service Python hiện có
"""
from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, FileResponse
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
import json
import logging
import uvicorn
import os
from pathlib import Path
from datetime import datetime

# Import existing services
from services.api_client import get_api_client, reinit_api_client
from core.config import get_config, GenerationConfig, API_PROVIDERS
from core.config_api import ConfigAPIManager
from services.novel_generator import (
    NovelGenerator, NovelProject, Chapter, OutlineParser,
    get_preset_templates, get_generator,
    get_cache_size, list_generation_caches, clear_generation_cache
)
from services.project_manager import ProjectManager
from services.genre_manager import GenreManager
from services.sub_genre_manager import SubGenreManager
from services.style_manager import StyleManager
from utils.exporter import export_to_docx, export_to_txt, export_to_markdown, export_to_html
from locales.i18n import t
from core.database import get_db
from core.state import app_state
from core.task_manager import task_manager, TaskStatus

logger = logging.getLogger(__name__)

app = FastAPI(title="TiniX Story API", version="1.0.0")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ==================== Request/Response Models ====================

class ProjectCreateReq(BaseModel):
    title: str
    genre: str
    sub_genres: List[str] = []
    character_setting: str = ""
    world_setting: str = ""
    plot_idea: str = ""

class SuggestionReq(BaseModel):
    type: str  # 'title', 'char', 'world', 'plot'
    genre: str = ""
    sub_genres: List[str] = []
    title: str = ""
    character_setting: str = ""
    world_setting: str = ""
    custom_prompt: str = ""
    num_main_chars: int = 2
    num_sub_chars: int = 3

class OutlineReq(BaseModel):
    title: str
    genre: str
    sub_genres: List[str] = []
    total_chapters: int = 20
    character_setting: str
    world_setting: str
    plot_idea: str
    custom_outline_prompt: str = ""

class ChapterGenReq(BaseModel):
    use_reflection: bool = False

class BulkGenReq(BaseModel):
    project_id: str
    chapter_nums: List[int]
    custom_prompt: str = ""
    use_reflection: bool = False

class SaveChapterReq(BaseModel):
    project_id: str
    chapter_num: int
    content: str

class RewriteReq(BaseModel):
    text: str
    style_template: str = ""
    use_reflection: bool = False

class PolishReq(BaseModel):
    text: str
    polish_type: str = "general"
    custom_requirements: str = ""
    use_reflection: bool = False

class SummaryReq(BaseModel):
    text: str
    max_length: int = 200

class ExportReq(BaseModel):
    project_id: str
    format: str = "txt"  # txt, docx, md, html

class GenParamsReq(BaseModel):
    temperature: Optional[float] = None
    top_p: Optional[float] = None
    max_tokens: Optional[int] = None
    chapter_target_words: Optional[int] = None
    writing_style: Optional[str] = None
    writing_tone: Optional[str] = None
    character_development: Optional[str] = None
    plot_complexity: Optional[str] = None

class BackendReq(BaseModel):
    name: str
    type: str = "openai"
    base_url: str = ""
    api_key: str = ""
    model: str = ""
    timeout: int = 120
    retry_times: int = 3
    enabled: bool = True

class GenreReq(BaseModel):
    name: str
    description: str = ""

class StyleReq(BaseModel):
    name: str
    description: str = ""

class UpdateOutlineReq(BaseModel):
    project_id: str
    outline_text: str

# ==================== Helpers ====================

def _project_to_dict(project: NovelProject) -> Dict:
    """Serialize NovelProject to JSON-safe dict"""
    return {
        "id": getattr(project, 'id', ''),
        "title": project.title,
        "genre": project.genre,
        "sub_genres": project.sub_genres if isinstance(project.sub_genres, list) else [],
        "character_setting": project.character_setting or "",
        "world_setting": project.world_setting or "",
        "plot_idea": project.plot_idea or "",
        "created_at": project.created_at,
        "updated_at": project.updated_at,
        "chapters": [
            {
                "num": ch.num,
                "title": ch.title,
                "desc": ch.desc,
                "content": ch.content or "",
                "word_count": ch.word_count,
                "generated_at": ch.generated_at
            }
            for ch in project.chapters
        ],
        "completed_count": project.get_completed_count(),
        "total_words": project.get_total_words(),
    }

def _get_generator() -> NovelGenerator:
    return app_state.get_generator()

# ==================== Health ====================

@app.get("/health")
async def health_check():
    return {"status": "ok", "version": "1.0.0"}

# ==================== Projects ====================

@app.get("/projects")
async def list_projects():
    return ProjectManager.list_projects()

@app.get("/projects/{project_id}")
async def get_project(project_id: str):
    project, msg = ProjectManager.load_project(project_id)
    if not project:
        raise HTTPException(status_code=404, detail=msg)
    return _project_to_dict(project)

@app.post("/projects")
async def create_project(req: ProjectCreateReq):
    project, msg = ProjectManager.create_project(
        req.title, req.genre, req.sub_genres,
        req.character_setting, req.world_setting, req.plot_idea
    )
    if not project:
        raise HTTPException(status_code=400, detail=msg)
    ProjectManager.save_project(project)
    return _project_to_dict(project)

@app.put("/projects/{project_id}")
async def update_project(project_id: str, req: ProjectCreateReq):
    project, msg = ProjectManager.load_project(project_id)
    if not project:
        raise HTTPException(status_code=404, detail=msg)
    project.title = req.title
    project.genre = req.genre
    project.sub_genres = req.sub_genres
    project.character_setting = req.character_setting
    project.world_setting = req.world_setting
    project.plot_idea = req.plot_idea
    ProjectManager.save_project(project)
    return _project_to_dict(project)

@app.delete("/projects/{project_id}")
async def delete_project(project_id: str):
    success, msg = ProjectManager.delete_project(project_id)
    if not success:
        raise HTTPException(status_code=404, detail=msg)
    return {"message": msg}

@app.post("/projects/update-outline")
async def update_outline(req: UpdateOutlineReq):
    project, msg = ProjectManager.load_project(req.project_id)
    if not project:
        raise HTTPException(status_code=404, detail=msg)
    chapters, parse_msg = OutlineParser.parse(req.outline_text)
    if not chapters:
        raise HTTPException(status_code=400, detail=parse_msg)
    # Preserve existing content for chapters that already have it
    old_content = {ch.num: ch for ch in project.chapters}
    for ch in chapters:
        if ch.num in old_content and old_content[ch.num].content:
            ch.content = old_content[ch.num].content
            ch.word_count = old_content[ch.num].word_count
            ch.generated_at = old_content[ch.num].generated_at
    project.chapters = chapters
    ProjectManager.save_project(project)
    return _project_to_dict(project)

# ==================== AI Suggestions ====================

@app.post("/suggest")
async def suggest(req: SuggestionReq):
    gen = _get_generator()
    if req.type == "title":
        content, msg = gen.suggest_title(req.genre, req.sub_genres, req.custom_prompt)
    else:
        content, msg = gen.suggest_content(
            req.type, req.title, req.genre, req.sub_genres,
            req.character_setting, req.world_setting, req.custom_prompt,
            req.num_main_chars, req.num_sub_chars
        )
    return {"content": content, "message": msg}

@app.post("/generate-outline")
async def generate_outline(req: OutlineReq):
    gen = _get_generator()
    content, msg = gen.generate_outline(
        req.title, req.genre, req.sub_genres, req.total_chapters,
        req.character_setting, req.world_setting, req.plot_idea,
        req.custom_outline_prompt
    )
    return {"content": content, "message": msg}

@app.post("/parse-outline")
async def parse_outline(data: Dict[str, str]):
    text = data.get("text", "")
    chapters, msg = OutlineParser.parse(text)
    return {"chapters": [c.to_dict() for c in chapters], "message": msg}

# ==================== Chapter Generation ====================

@app.post("/generate-chapter")
async def generate_chapter(req: ChapterGenReq):
    project, msg = ProjectManager.load_project(req.project_id)
    if not project:
        raise HTTPException(status_code=404, detail=msg)

    chapter = next((c for c in project.chapters if c.num == req.chapter_num), None)
    if not chapter:
        raise HTTPException(status_code=404, detail=f"Chapter {req.chapter_num} not found")

    prev_chapters = [c for c in project.chapters if c.num < req.chapter_num and c.content]
    previous_content = ""
    if prev_chapters:
        sorted_prev = sorted(prev_chapters, key=lambda x: x.num)
        previous_content = sorted_prev[-1].content[-3000:] if sorted_prev[-1].content else ""

    gen = _get_generator()
    content, gen_msg = gen.generate_chapter(
        req.chapter_num, chapter.title, chapter.desc, project.title,
        project.character_setting, project.world_setting, project.plot_idea,
        project.genre, project.sub_genres, previous_content,
        custom_prompt=req.custom_prompt, use_reflection=req.use_reflection
    )

    if content:
        chapter.content = content
        chapter.word_count = len(content)
        chapter.generated_at = datetime.now().isoformat()
        ProjectManager.save_project(project)

    return {"content": content, "message": gen_msg, "word_count": len(content) if content else 0}

@app.post("/generate-chapter-stream")
async def generate_chapter_stream(req: ChapterGenReq):
    project, msg = ProjectManager.load_project(req.project_id)
    if not project:
        raise HTTPException(status_code=404, detail=msg)

    chapter = next((c for c in project.chapters if c.num == req.chapter_num), None)
    if not chapter:
        raise HTTPException(status_code=404, detail=f"Chapter {req.chapter_num} not found")

    prev_chapters = [c for c in project.chapters if c.num < req.chapter_num and c.content]
    previous_content = ""
    if prev_chapters:
        sorted_prev = sorted(prev_chapters, key=lambda x: x.num)
        previous_content = sorted_prev[-1].content[-3000:] if sorted_prev[-1].content else ""

    gen = _get_generator()

    async def event_generator():
        full_content = ""
        for success, chunk in gen.generate_chapter_stream(
            req.chapter_num, chapter.title, chapter.desc, project.title,
            project.character_setting, project.world_setting, project.plot_idea,
            project.genre, project.sub_genres, previous_content,
            custom_prompt=req.custom_prompt, use_reflection=req.use_reflection
        ):
            if success:
                full_content += chunk
                yield f"data: {json.dumps({'chunk': chunk})}\n\n"
            else:
                yield f"data: {json.dumps({'error': chunk})}\n\n"

        # Auto-save after streaming completes
        if full_content:
            chapter.content = full_content
            chapter.word_count = len(full_content)
            chapter.generated_at = datetime.now().isoformat()
            ProjectManager.save_project(project)

        yield "data: [DONE]\n\n"

    return StreamingResponse(event_generator(), media_type="text/event-stream")

@app.post("/save-chapter")
async def save_chapter(req: SaveChapterReq):
    project, msg = ProjectManager.load_project(req.project_id)
    if not project:
        raise HTTPException(status_code=404, detail=msg)

    found = False
    for ch in project.chapters:
        if ch.num == req.chapter_num:
            ch.content = req.content
            ch.word_count = len(req.content)
            ch.generated_at = datetime.now().isoformat()
            found = True
            break

    if not found:
        raise HTTPException(status_code=404, detail=f"Chapter {req.chapter_num} not found")

    ProjectManager.save_project(project)
    return {"message": "Chapter saved", "word_count": len(req.content)}

# ==================== Writing Tools ====================

@app.post("/rewrite")
async def rewrite(req: RewriteReq):
    gen = _get_generator()
    content, msg = gen.rewrite_paragraph(req.text, req.style_template, req.use_reflection)
    return {"content": content, "message": msg}

@app.post("/polish")
async def polish(req: PolishReq):
    gen = _get_generator()
    content, msg = gen.polish_text(req.text, req.polish_type, req.custom_requirements, req.use_reflection)
    return {"content": content, "message": msg}

@app.post("/summary")
async def summary(req: SummaryReq):
    gen = _get_generator()
    content, msg = gen.generate_summary(req.text, req.max_length)
    return {"content": content, "message": msg}

@app.post("/export")
async def export_project(req: ExportReq):
    project, msg = ProjectManager.load_project(req.project_id)
    if not project:
        raise HTTPException(status_code=404, detail=msg)

    full_text = f"# {project.title}\n\n"
    for ch in project.chapters:
        if ch.content:
            full_text += f"## Chương {ch.num}: {ch.title}\n\n"
            full_text += ch.content + "\n\n"

    if len(full_text.strip()) < 50:
        raise HTTPException(status_code=400, detail="No content to export")

    export_map = {
        "docx": export_to_docx,
        "txt": export_to_txt,
        "md": export_to_markdown,
        "html": export_to_html,
    }
    exporter = export_map.get(req.format)
    if not exporter:
        raise HTTPException(status_code=400, detail=f"Unsupported format: {req.format}")

    filepath, exp_msg = exporter(full_text, project.title)
    if not filepath:
        raise HTTPException(status_code=500, detail=exp_msg)

    return FileResponse(filepath, filename=os.path.basename(filepath), media_type="application/octet-stream")

# ==================== Background Task Coroutines ====================

async def generate_bulk_task(task, req: BulkGenReq):
    project, msg = ProjectManager.load_project(req.project_id)
    if not project:
        task.update(status=TaskStatus.FAILED, message=msg)
        return

    gen = _get_generator()
    total = len(req.chapter_nums)
    
    for i, ch_num in enumerate(req.chapter_nums):
        if task.is_cancelled():
            break
            
        task.update(message=f"Generating chapter {ch_num} ({i+1}/{total})", progress=(i / total) * 100)
        
        chapter = next((c for c in project.chapters if c.num == ch_num), None)
        if not chapter:
            logger.error(f"Chapter {ch_num} not found in project {req.project_id}")
            continue

        # Get context from previous chapters
        prev_chapters = [c for c in project.chapters if c.num < ch_num and c.content]
        previous_content = ""
        if prev_chapters:
            sorted_prev = sorted(prev_chapters, key=lambda x: x.num)
            previous_content = sorted_prev[-1].content[-3000:] if sorted_prev[-1].content else ""

        # Run generation
        # Note: NovelGenerator.generate_chapter is currently sync. 
        # In a real async app we should make it async, but for now we run it in a thread if needed.
        # Since this is already in a background task, it's okay for now.
        content, gen_msg = await asyncio.to_thread(
            gen.generate_chapter,
            ch_num, chapter.title, chapter.desc, project.title,
            project.character_setting, project.world_setting, project.plot_idea,
            project.genre, project.sub_genres, previous_content,
            custom_prompt=req.custom_prompt, use_reflection=req.use_reflection
        )

        if content:
            chapter.content = content
            chapter.word_count = len(content)
            chapter.generated_at = datetime.now().isoformat()
            ProjectManager.save_project(project)
        else:
            logger.error(f"Failed to generate chapter {ch_num}: {gen_msg}")

    if not task.is_cancelled():
        task.update(status=TaskStatus.COMPLETED, progress=100.0, message=f"Successfully generated {total} chapters")

# ==================== Projects/Tasks Endpoints Extensions ====================

@app.post("/tasks/generate-bulk")
async def start_bulk_generation(req: BulkGenReq):
    task = await task_manager.create_task(
        name=f"Bulk Generation for {req.project_id}",
        task_type="generate_bulk",
        metadata={"project_id": req.project_id, "chapters": req.chapter_nums}
    )
    
    # Start the task in background
    asyncio.create_task(task_manager.run_task(task.id, generate_bulk_task, req))
    
    return {"task_id": task.id, "message": "Bulk generation started"}

# ==================== Genres ====================

@app.get("/genres")
async def list_genres():
    return GenreManager.list_genres()

@app.post("/genres")
async def add_genre(req: GenreReq):
    success = GenreManager.add_genre(req.name, req.description)
    if not success:
        raise HTTPException(status_code=400, detail="Genre already exists or error")
    return {"message": "Genre added", "name": req.name}

@app.put("/genres/{name}")
async def update_genre(name: str, req: GenreReq):
    success = GenreManager.update_genre(name, req.name, req.description)
    if not success:
        raise HTTPException(status_code=400, detail="Update failed")
    return {"message": "Genre updated"}

@app.delete("/genres/{name}")
async def delete_genre(name: str):
    success = GenreManager.delete_genre(name)
    if not success:
        raise HTTPException(status_code=400, detail="Delete failed")
    return {"message": "Genre deleted"}

# ==================== Sub-Genres ====================

@app.get("/sub-genres")
async def list_all_sub_genres():
    return SubGenreManager.get_sub_genre_names()

@app.get("/sub-genres/by-genre/{genre}")
async def list_sub_genres_by_genre(genre: str):
    return SubGenreManager.get_sub_genres_by_genre(genre)

@app.post("/sub-genres")
async def add_sub_genre(req: GenreReq):
    success = SubGenreManager.add_sub_genre(req.name, req.description)
    if not success:
        raise HTTPException(status_code=400, detail="Sub-genre already exists or error")
    return {"message": "Sub-genre added", "name": req.name}

@app.put("/sub-genres/{name}")
async def update_sub_genre(name: str, req: GenreReq):
    success = SubGenreManager.update_sub_genre(name, req.name, req.description)
    if not success:
        raise HTTPException(status_code=400, detail="Update failed")
    return {"message": "Sub-genre updated"}

@app.delete("/sub-genres/{name}")
async def delete_sub_genre(name: str):
    success = SubGenreManager.delete_sub_genre(name)
    if not success:
        raise HTTPException(status_code=400, detail="Delete failed")
    return {"message": "Sub-genre deleted"}

# ==================== Styles ====================

@app.get("/styles")
async def list_styles():
    return StyleManager.get_style_names()

@app.get("/styles/all")
async def list_styles_full():
    return StyleManager.load_styles()

@app.post("/styles")
async def add_style(req: StyleReq):
    success = StyleManager.add_style(req.name, req.description)
    if not success:
        raise HTTPException(status_code=400, detail="Style already exists or error")
    return {"message": "Style added", "name": req.name}

@app.put("/styles/{name}")
async def update_style(name: str, req: StyleReq):
    success = StyleManager.update_style(name, req.name, req.description)
    if not success:
        raise HTTPException(status_code=400, detail="Update failed")
    return {"message": "Style updated"}

@app.delete("/styles/{name}")
async def delete_style(name: str):
    success = StyleManager.delete_style(name)
    if not success:
        raise HTTPException(status_code=400, detail="Delete failed")
    return {"message": "Style deleted"}

# ==================== Config / Settings ====================

@app.get("/config/backends")
async def config_list_backends():
    return ConfigAPIManager.list_backends()

@app.post("/config/backends")
async def config_add_backend(req: BackendReq):
    result = ConfigAPIManager.add_backend(
        req.name, req.type, req.base_url, req.api_key,
        req.model, req.timeout, req.retry_times, req.enabled
    )
    if result["success"]:
        reinit_api_client()
        app_state.generator = None
    return result

@app.put("/config/backends/{name}")
async def config_update_backend(name: str, req: BackendReq):
    result = ConfigAPIManager.update_backend(
        name, new_name=req.name, type=req.type, base_url=req.base_url,
        api_key=req.api_key, model=req.model, timeout=req.timeout
    )
    if result["success"]:
        reinit_api_client()
        app_state.generator = None
    return result

@app.delete("/config/backends/{name}")
async def config_delete_backend(name: str):
    result = ConfigAPIManager.delete_backend(name)
    if result["success"]:
        reinit_api_client()
        app_state.generator = None
    return result

@app.post("/config/backends/{name}/test")
async def config_test_backend(name: str):
    return ConfigAPIManager.test_backend(name)

@app.get("/config/generation")
async def config_get_generation():
    cfg = get_config()
    gen = cfg.generation
    return {
        "temperature": gen.temperature,
        "top_p": gen.top_p,
        "max_tokens": gen.max_tokens,
        "chapter_target_words": gen.chapter_target_words,
        "writing_style": gen.writing_style,
        "writing_tone": gen.writing_tone,
        "character_development": gen.character_development,
        "plot_complexity": gen.plot_complexity,
    }

@app.put("/config/generation")
async def config_update_generation(req: GenParamsReq):
    cfg = get_config()
    params = {k: v for k, v in req.model_dump().items() if v is not None}
    success, msg = cfg.update_generation_config(**params)
    if success:
        app_state.generator = None
    return {"success": success, "message": msg}

@app.get("/config/providers")
async def config_list_providers():
    return API_PROVIDERS

# ==================== Cache ====================

@app.get("/cache/stats")
async def cache_stats():
    try:
        api_client = get_api_client()
        stats = api_client.get_cache_stats()
        gen_caches = list_generation_caches()
        gen_size = get_cache_size()
        return {
            "api_cache": stats,
            "generation_cache_count": len(gen_caches),
            "generation_cache_size_kb": round(gen_size / 1024, 1)
        }
    except Exception as e:
        return {"error": str(e)}

@app.delete("/cache")
async def clear_cache():
    try:
        api_client = get_api_client()
        api_client.clear_cache()
        clear_generation_cache()
        return {"message": "All caches cleared"}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# ==================== Task Management ====================

@app.get("/tasks")
async def list_tasks():
    return task_manager.list_tasks()

@app.get("/tasks/{task_id}")
async def get_task_status(task_id: str):
    task = task_manager.get_task(task_id)
    if not task:
        raise HTTPException(status_code=404, detail="Task not found")
    return task.to_dict()

@app.delete("/tasks/{task_id}")
async def cancel_task(task_id: str):
    task = task_manager.get_task(task_id)
    if not task:
        raise HTTPException(status_code=404, detail="Task not found")
    task.cancel()
    return {"message": "Task cancellation requested"}

# ==================== Entry ====================

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
