"""Prompt history model for version tracking."""

import difflib
import json
import time
import uuid
from typing import Optional

from open_webui.internal.db import Base, get_async_db_context
from open_webui.models.users import UserResponse, Users
from pydantic import BaseModel, ConfigDict
from sqlalchemy import JSON, BigInteger, Column, Index, Text, delete, func, select
from sqlalchemy.ext.asyncio import AsyncSession

####################
# PromptHistory DB Schema
####################


class PromptHistory(Base):
    __tablename__ = 'prompt_history'

    id = Column(Text, primary_key=True)
    prompt_id = Column(Text, nullable=False, index=True)
    parent_id = Column(Text, nullable=True)  # Reference to parent commit
    snapshot = Column(JSON, nullable=False)
    user_id = Column(Text, nullable=False)
    commit_message = Column(Text, nullable=True)
    created_at = Column(BigInteger, nullable=False)


class PromptHistoryModel(BaseModel):
    id: str
    prompt_id: str
    parent_id: Optional[str] = None
    snapshot: dict
    user_id: str
    commit_message: Optional[str] = None
    created_at: int

    model_config = ConfigDict(from_attributes=True)


class PromptHistoryResponse(PromptHistoryModel):
    """Response model with user info."""

    user: Optional[UserResponse] = None


class PromptHistoryTable:
    async def create_history_entry(
        self,
        prompt_id: str,
        snapshot: dict,
        user_id: str,
        parent_id: Optional[str] = None,
        commit_message: Optional[str] = None,
        db: Optional[AsyncSession] = None,
    ) -> Optional[PromptHistoryModel]:
        """Create a new history entry (commit) for a prompt."""
        async with get_async_db_context(db) as db:
            history = PromptHistory(
                id=str(uuid.uuid4()),
                prompt_id=prompt_id,
                parent_id=parent_id,
                snapshot=snapshot,
                user_id=user_id,
                commit_message=commit_message,
                created_at=int(time.time()),
            )
            db.add(history)
            await db.commit()
            await db.refresh(history)
            return PromptHistoryModel.model_validate(history)

    async def get_history_by_prompt_id(
        self,
        prompt_id: str,
        limit: int = 50,
        offset: int = 0,
        db: Optional[AsyncSession] = None,
    ) -> list[PromptHistoryResponse]:
        """Get all history entries for a prompt, ordered by created_at desc."""
        async with get_async_db_context(db) as db:
            result = await db.execute(
                select(PromptHistory)
                .filter(PromptHistory.prompt_id == prompt_id)
                .order_by(PromptHistory.created_at.desc())
                .offset(offset)
                .limit(limit)
            )
            entries = result.scalars().all()

            # Get user info for each entry
            user_ids = list(set(e.user_id for e in entries))
            users = await Users.get_users_by_user_ids(user_ids, db=db) if user_ids else []
            users_dict = {user.id: user for user in users}

            return [
                PromptHistoryResponse(
                    **PromptHistoryModel.model_validate(entry).model_dump(),
                    user=(users_dict.get(entry.user_id).model_dump() if users_dict.get(entry.user_id) else None),
                )
                for entry in entries
            ]

    async def get_history_entry_by_id(
        self,
        history_id: str,
        db: Optional[AsyncSession] = None,
    ) -> Optional[PromptHistoryModel]:
        """Get a specific history entry by ID."""
        async with get_async_db_context(db) as db:
            result = await db.execute(select(PromptHistory).filter(PromptHistory.id == history_id))
            entry = result.scalars().first()
            if entry:
                return PromptHistoryModel.model_validate(entry)
            return None

    async def get_latest_history_entry(
        self,
        prompt_id: str,
        db: Optional[AsyncSession] = None,
    ) -> Optional[PromptHistoryModel]:
        """Get the most recent history entry for a prompt."""
        async with get_async_db_context(db) as db:
            result = await db.execute(
                select(PromptHistory)
                .filter(PromptHistory.prompt_id == prompt_id)
                .order_by(PromptHistory.created_at.desc())
                .limit(1)
            )
            entry = result.scalars().first()
            if entry:
                return PromptHistoryModel.model_validate(entry)
            return None

    async def get_history_count(
        self,
        prompt_id: str,
        db: Optional[AsyncSession] = None,
    ) -> int:
        """Get the number of history entries for a prompt."""
        async with get_async_db_context(db) as db:
            result = await db.execute(
                select(func.count()).select_from(PromptHistory).filter(PromptHistory.prompt_id == prompt_id)
            )
            return result.scalar()

    async def compute_diff(
        self,
        from_id: str,
        to_id: str,
        prompt_id: str,
        db: Optional[AsyncSession] = None,
    ) -> Optional[dict]:
        """Compute diff between two history entries."""
        async with get_async_db_context(db) as db:
            # Bind both entries to the authorized prompt; an unbound id reads another prompt's snapshot.
            result_from = await db.execute(
                select(PromptHistory).filter(PromptHistory.id == from_id, PromptHistory.prompt_id == prompt_id)
            )
            from_entry = result_from.scalars().first()
            result_to = await db.execute(
                select(PromptHistory).filter(PromptHistory.id == to_id, PromptHistory.prompt_id == prompt_id)
            )
            to_entry = result_to.scalars().first()

            if not from_entry or not to_entry:
                return None

            from_snapshot = from_entry.snapshot
            to_snapshot = to_entry.snapshot

            # Compute diff for content field
            from_content = from_snapshot.get('content', '')
            to_content = to_snapshot.get('content', '')

            diff_lines = list(
                difflib.unified_diff(
                    from_content.splitlines(keepends=True),
                    to_content.splitlines(keepends=True),
                    fromfile=f'v{from_id[:8]}',
                    tofile=f'v{to_id[:8]}',
                    lineterm='',
                )
            )

            return {
                'from_id': from_id,
                'to_id': to_id,
                'from_snapshot': from_snapshot,
                'to_snapshot': to_snapshot,
                'content_diff': diff_lines,
                'name_changed': from_snapshot.get('name') != to_snapshot.get('name'),
            }

    async def delete_history_by_prompt_id(
        self,
        prompt_id: str,
        db: Optional[AsyncSession] = None,
    ) -> bool:
        """Delete all history entries for a prompt."""
        async with get_async_db_context(db) as db:
            await db.execute(delete(PromptHistory).filter(PromptHistory.prompt_id == prompt_id))
            await db.commit()
            return True

    async def delete_history_entry(
        self,
        history_id: str,
        prompt_id: str,
        db: Optional[AsyncSession] = None,
    ) -> bool:
        """Delete a history entry and reparent its children to grandparent."""
        async with get_async_db_context(db) as db:
            # Bind to the authorized prompt; an unbound id deletes another prompt's history.
            result = await db.execute(select(PromptHistory).filter_by(id=history_id, prompt_id=prompt_id))
            entry = result.scalars().first()
            if not entry:
                return False

            # Find children that reference this entry as parent
            children_result = await db.execute(select(PromptHistory).filter_by(parent_id=history_id))
            children = children_result.scalars().all()

            # Reparent children to grandparent
            for child in children:
                child.parent_id = entry.parent_id

            await db.delete(entry)
            await db.commit()
            return True


PromptHistories = PromptHistoryTable()
