from typing import Optional
import aiosqlite
from database.db import DB_PATH

DEFAULT_PERSONAS = {
    "default": {
        "name": "AI Ассистент",
        "emoji": "🤖",
        "description": "Универсальный помощник",
        "prompt": "Ты — полезный AI ассистент. Отвечай чётко и по делу.",
        "sd_enabled": False,
    },
    "rpg_master": {
        "name": "Мастер RPG",
        "emoji": "🧙",
        "description": "Ведёт ролевые игры, создаёт атмосферу",
        "prompt": """Ты — опытный Мастер ролевых игр.
Создавай живые описания, веди нарратив, реагируй на действия игрока.
Мир детальный, персонажи запоминающиеся.
Отвечай только текстом сюжета — без тегов изображений.""",
        "sd_enabled": True,
    },
    "villain": {
        "name": "Злодей",
        "emoji": "😈",
        "description": "Харизматичный антагонист",
        "prompt": """Ты — харизматичный злодей с грандиозными планами.
Говоришь театрально, с сарказмом и превосходством.
Никогда не выходишь из роли. Называешь собеседника 'герой' с иронией.""",
        "sd_enabled": False,
    },
    "scientist": {
        "name": "Учёный",
        "emoji": "🔬",
        "description": "Объясняет сложное простыми словами",
        "prompt": """Ты — увлечённый учёный. Объясняешь любые темы
через факты, аналогии и примеры. Любишь уточнять детали.
Иногда уходишь в интересные отступления.""",
        "sd_enabled": False,
    },
    "samurai": {
        "name": "Самурай",
        "emoji": "⚔️",
        "description": "Мудрый воин феодальной Японии",
        "prompt": """Ты — самурай феодальной Японии.
Говоришь кратко, мудро, с достоинством.
Используешь метафоры природы и войны.
Чтишь кодекс бусидо.""",
        "sd_enabled": True,
        "appearance_tags": "samurai armor, katana, feudal japan",
    },
}


def _row_to_persona(row: dict) -> dict:
    return {
        "name": row["name"],
        "emoji": row["emoji"],
        "description": row["description"],
        "prompt": row["prompt"],
        "custom": bool(row["custom"]),
        "sd_enabled": bool(row["sd_enabled"]),
        "lora_name": row["lora_name"] or "",
        "lora_weight": row["lora_weight"] if row["lora_weight"] is not None else 0.8,
        "appearance_tags": row["appearance_tags"] or "",
        "appearance_prose": row.get("appearance_prose", "") or "",
        "personality": row.get("personality", "") or "",
        "scenario": row.get("scenario", "") or "",
        "first_mes": row.get("first_mes", "") or "",
        "mes_example": row.get("mes_example", "") or "",
        "lorebook_json": row.get("lorebook_json", "[]") or "[]",
        "avatar_path": row.get("avatar_path", "") or "",
        "alternate_greetings_json": row.get("alternate_greetings_json", "[]") or "[]",
    }


def build_persona_prompt(data: dict) -> str:
    parts = [
        f"You are {data.get('name', '').strip()}." if data.get("name") else "",
        f"Description: {data.get('description', '').strip()}",
        f"Personality: {data.get('personality', '').strip()}",
        f"Scenario: {data.get('scenario', '').strip()}",
    ]
    ex = (data.get("mes_example") or "").strip()
    if ex:
        parts.append(f"Example dialogue:\n{ex}")
    parts.append("Stay in character. Reply as the character. Do not add image tags.")
    from services.chat_prompt import ROLEPLAY_GUARDRAILS

    parts.append(ROLEPLAY_GUARDRAILS)
    return "\n\n".join(p for p in parts if p and p.split(": ", 1)[-1].strip())


async def get_all_personas() -> dict:
    async with aiosqlite.connect(DB_PATH) as db:
        db.row_factory = aiosqlite.Row
        async with db.execute("SELECT * FROM personas ORDER BY custom ASC, persona_id ASC") as cur:
            rows = await cur.fetchall()
    return {r["persona_id"]: _row_to_persona(dict(r)) for r in rows}


async def get_persona(persona_id: str) -> Optional[dict]:
    async with aiosqlite.connect(DB_PATH) as db:
        db.row_factory = aiosqlite.Row
        async with db.execute(
            "SELECT * FROM personas WHERE persona_id = ?", (persona_id,)
        ) as cur:
            row = await cur.fetchone()
    if not row:
        return None
    return _row_to_persona(dict(row))


async def create_persona(
    persona_id: str,
    name: str,
    emoji: str,
    description: str,
    prompt: str,
    sd_enabled: bool = False,
    lora_name: str = "",
    lora_weight: float = 0.8,
    appearance_tags: str = "",
    appearance_prose: str = "",
    personality: str = "",
    scenario: str = "",
    first_mes: str = "",
    mes_example: str = "",
    lorebook_json: str = "[]",
    avatar_path: str = "",
    alternate_greetings_json: str = "[]",
) -> dict:
    final_prompt = prompt.strip() or build_persona_prompt(
        {
            "name": name,
            "description": description,
            "personality": personality,
            "scenario": scenario,
            "mes_example": mes_example,
        }
    )
    async with aiosqlite.connect(DB_PATH) as db:
        await db.execute(
            """INSERT INTO personas
               (persona_id, name, emoji, description, prompt, custom,
                sd_enabled, lora_name, lora_weight, appearance_tags, appearance_prose,
                personality, scenario, first_mes, mes_example, lorebook_json, avatar_path,
                alternate_greetings_json)
               VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
            (
                persona_id, name, emoji, description, final_prompt,
                1 if sd_enabled else 0, lora_name, lora_weight, appearance_tags, appearance_prose,
                personality, scenario, first_mes, mes_example, lorebook_json, avatar_path,
                alternate_greetings_json,
            ),
        )
        await db.commit()
        return {
        "name": name,
        "emoji": emoji,
        "description": description,
        "prompt": final_prompt,
        "custom": True,
        "sd_enabled": sd_enabled,
        "lora_name": lora_name,
        "lora_weight": lora_weight,
        "appearance_tags": appearance_tags,
        "appearance_prose": appearance_prose,
        "personality": personality,
        "scenario": scenario,
        "first_mes": first_mes,
        "mes_example": mes_example,
        "lorebook_json": lorebook_json,
        "avatar_path": avatar_path,
        "alternate_greetings_json": alternate_greetings_json,
    }


async def delete_persona(persona_id: str) -> bool:
    async with aiosqlite.connect(DB_PATH) as db:
        async with db.execute(
            "SELECT custom FROM personas WHERE persona_id = ?", (persona_id,)
        ) as cur:
            row = await cur.fetchone()
        if not row or not row[0]:
            return False
        await db.execute("DELETE FROM personas WHERE persona_id = ?", (persona_id,))
        await db.commit()

    if persona_id.startswith("card_"):
        from services.character_card import delete_character
        await delete_character(persona_id[5:])

    return True


async def update_persona_appearance(persona_id: str, appearance_tags: str):
    async with aiosqlite.connect(DB_PATH) as db:
        await db.execute(
            "UPDATE personas SET appearance_tags = ? WHERE persona_id = ?",
            (appearance_tags, persona_id),
        )
        await db.commit()


async def update_persona_lora(persona_id: str, lora_name: str | None, lora_weight: float | None):
    fields, vals = [], []
    if lora_name is not None:
        fields.append("lora_name = ?"); vals.append(lora_name)
    if lora_weight is not None:
        fields.append("lora_weight = ?"); vals.append(lora_weight)
    if not fields:
        return
    async with aiosqlite.connect(DB_PATH) as db:
        await db.execute(f"UPDATE personas SET {', '.join(fields)} WHERE persona_id = ?", (*vals, persona_id))
        await db.commit()


async def update_persona_prompt(persona_id: str, prompt: str):
    async with aiosqlite.connect(DB_PATH) as db:
        await db.execute("UPDATE personas SET prompt = ? WHERE persona_id = ?", (prompt, persona_id))
        await db.commit()


async def patch_persona(persona_id: str, fields: dict) -> bool:
    allowed = {
        "name",
        "emoji",
        "description",
        "prompt",
        "sd_enabled",
        "lora_name",
        "lora_weight",
        "appearance_tags",
        "appearance_prose",
        "personality",
        "scenario",
        "first_mes",
        "mes_example",
        "lorebook_json",
        "avatar_path",
        "alternate_greetings_json",
    }
    updates = {k: v for k, v in fields.items() if k in allowed}
    if not updates:
        return False

    async with aiosqlite.connect(DB_PATH) as db:
        db.row_factory = aiosqlite.Row
        # disallow editing built-in personas
        async with db.execute("SELECT custom FROM personas WHERE persona_id = ?", (persona_id,)) as cur:
            row = await cur.fetchone()
        if not row or not row[0]:
            return False

        # rebuild prompt if user didn't explicitly set it
        raw_fields = {"name", "description", "personality", "scenario", "mes_example"}
        if "prompt" not in updates and (raw_fields & updates.keys()):
            async with db.execute("SELECT * FROM personas WHERE persona_id = ?", (persona_id,)) as cur:
                existing = await cur.fetchone()
            if existing:
                merged = dict(existing)
                merged.update(updates)
                updates["prompt"] = build_persona_prompt(merged)
        
        if "appearance_tags" in updates and "appearance_prose" not in updates:
            tags = updates["appearance_tags"].strip()
            if tags:
                from services.llm import send_message
                try:
                    prose = await send_message([
                        {"role": "system", "content": "Convert danbooru tags to natural English description. Output only the description, no markdown."},
                        {"role": "user", "content": f"Tags: {tags}"}
                    ])
                    updates["appearance_prose"] = prose.strip()
                except Exception:
                    pass

        cols = ", ".join(f"{k} = ?" for k in updates)
        cur2 = await db.execute(
            f"UPDATE personas SET {cols} WHERE persona_id = ?",
            (*updates.values(), persona_id),
        )
        await db.commit()
        return cur2.rowcount > 0
