"""
Chat CRUD + message streaming — v4.3.0 with GitLab for users, branch auto-create, commit fixes.
"""

import json
from datetime import datetime
from pydantic import BaseModel
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session

from backend.database import get_db
from backend.models import User, Chat, Message, ChatAttachment, LinkedRepo, GitLabSettings
from backend.auth import (
    get_current_user, get_user_permissions, check_feature,
    check_model_allowed, count_user_chats, count_user_messages_today,
)
from backend.services import attachment_service, gitlab_service
from backend.services.generation_manager import manager as gen_manager

router = APIRouter()


class CreateChatBody(BaseModel):
    title: str = "New Chat"
    model: str = "eu.anthropic.claude-opus-4-6-v1"
    knowledge_base_id: Optional[str] = None
    linked_repo_id: Optional[str] = None
    max_tokens: int = 4096
    reasoning_budget: int = 0


class UpdateChatBody(BaseModel):
    title: Optional[str] = None
    model: Optional[str] = None
    max_tokens: Optional[int] = None
    reasoning_budget: Optional[int] = None
    knowledge_base_id: Optional[str] = None
    linked_repo_id: Optional[str] = None


class SendMessageBody(BaseModel):
    content: str
    model: Optional[str] = None
    max_tokens: int = 4096
    reasoning_budget: int = 0
    knowledge_base_id: Optional[str] = None
    attachment_ids: list[str] = []
    web_search: bool = False


class CommitFromChatBody(BaseModel):
    branch: str
    commit_message: str
    files: list[dict]
    create_branch_if_missing: bool = True


# ═══════════════════════════════════════════════════
#  User-facing: Available Repos & Branches
# ═══════════════════════════════════════════════════

@router.get("/available-repos")
def list_available_repos(user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    """List linked repos that any user with gitlab permission can select for chats."""
    perms = get_user_permissions(user.id, db)
    if not perms.get("can_use_gitlab"):
        return []
    repos = db.query(LinkedRepo).order_by(LinkedRepo.created_at.desc()).all()
    return [_repo_brief(r) for r in repos]


@router.get("/repos/{repo_id}/branches")
async def get_repo_branches(repo_id: str, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    """Get branches for a linked repo — available to users with gitlab permission."""
    check_feature(user.id, "use_gitlab", db)
    repo = db.query(LinkedRepo).filter(LinkedRepo.id == repo_id).first()
    if not repo:
        raise HTTPException(404, "Repository not found")
    settings = db.query(GitLabSettings).first()
    if not settings or not settings.is_active:
        raise HTTPException(400, "GitLab not configured")
    try:
        branches = await gitlab_service.list_branches(
            settings.gitlab_url, settings.gitlab_token, repo.gitlab_project_id
        )
        return branches
    except gitlab_service.GitLabError as e:
        raise HTTPException(e.status_code, e.detail)


# ═══════════════════════════════════════════════════
#  Chat CRUD
# ═══════════════════════════════════════════════════

@router.get("")
def list_chats(user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    chats = db.query(Chat).filter(Chat.user_id == user.id).order_by(Chat.updated_at.desc()).all()
    return [_chat_dict(c, db) for c in chats]


@router.post("")
def create_chat(body: CreateChatBody, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    perms = get_user_permissions(user.id, db)
    max_chats = perms.get("max_chats", 0)
    if max_chats > 0:
        current_count = count_user_chats(user.id, db)
        if current_count >= max_chats:
            raise HTTPException(403, f"Chat limit reached ({max_chats}). Delete old chats or contact admin.")

    if body.knowledge_base_id:
        if not perms.get("can_use_knowledge_base"):
            raise HTTPException(403, "Knowledge base access not enabled for your account.")

    if body.linked_repo_id:
        if not perms.get("can_use_gitlab"):
            raise HTTPException(403, "GitLab access not enabled for your account.")

    chat = Chat(
        user_id=user.id, title=body.title,
        model=check_model_allowed(user.id, body.model, db),
        knowledge_base_id=body.knowledge_base_id or None,
        linked_repo_id=body.linked_repo_id or None,
        max_tokens=min(body.max_tokens, perms.get("max_tokens_cap", 4096)),
        reasoning_budget=min(body.reasoning_budget, perms.get("max_reasoning_budget", 0)),
    )
    db.add(chat); db.commit(); db.refresh(chat)
    return _chat_dict(chat, db)


@router.get("/{chat_id}")
def get_chat(chat_id: str, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
    if not chat: raise HTTPException(404, "Chat not found")
    return _chat_dict(chat, db)


@router.put("/{chat_id}")
def update_chat(chat_id: str, body: UpdateChatBody, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
    if not chat: raise HTTPException(404)
    perms = get_user_permissions(user.id, db)
    if body.title is not None: chat.title = body.title
    if body.model is not None: chat.model = check_model_allowed(user.id, body.model, db)
    if body.max_tokens is not None: chat.max_tokens = min(body.max_tokens, perms.get("max_tokens_cap", 4096))
    if body.reasoning_budget is not None: chat.reasoning_budget = min(body.reasoning_budget, perms.get("max_reasoning_budget", 0))
    if body.knowledge_base_id is not None:
        if body.knowledge_base_id and not perms.get("can_use_knowledge_base"):
            raise HTTPException(403, "Knowledge base not enabled.")
        chat.knowledge_base_id = body.knowledge_base_id or None
    if body.linked_repo_id is not None:
        if body.linked_repo_id and not perms.get("can_use_gitlab"):
            raise HTTPException(403, "GitLab not enabled.")
        chat.linked_repo_id = body.linked_repo_id or None
    db.commit()
    return _chat_dict(chat, db)


@router.delete("/{chat_id}")
def delete_chat(chat_id: str, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
    if not chat: raise HTTPException(404)
    attachment_service.delete_chat_attachments(chat_id)
    db.delete(chat); db.commit()
    return {"ok": True}


@router.get("/{chat_id}/messages")
def get_messages(chat_id: str, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
    if not chat: raise HTTPException(404)
    msgs = []
    for m in chat.messages:
        d = _msg_dict(m)
        atts = db.query(ChatAttachment).filter(ChatAttachment.message_id == m.id).all()
        d["attachments"] = [_att_brief(a) for a in atts]
        msgs.append(d)
    return msgs


# ═══════════════════════════════════════════════════
#  Streaming & Generation
# ═══════════════════════════════════════════════════

@router.get("/{chat_id}/generating")
def check_generating(chat_id: str, user: User = Depends(get_current_user)):
    return {"active": gen_manager.is_active(chat_id)}


@router.get("/{chat_id}/stream")
async def reconnect_stream(chat_id: str, user: User = Depends(get_current_user)):
    if not gen_manager.is_active(chat_id):
        async def empty():
            yield _sse({"type": "done", "message_id": ""})
        return StreamingResponse(empty(), media_type="text/event-stream")
    async def generate():
        async for event in gen_manager.stream_events(chat_id):
            yield _sse(event)
    return StreamingResponse(generate(), media_type="text/event-stream")


@router.post("/{chat_id}/messages")
async def send_message(chat_id: str, body: SendMessageBody, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    perms = get_user_permissions(user.id, db)

    max_per_day = perms.get("max_messages_per_day", 0)
    if max_per_day > 0:
        today_count = count_user_messages_today(user.id, db)
        if today_count >= max_per_day:
            raise HTTPException(429, f"Daily message limit reached ({max_per_day}). Try again tomorrow.")

    web_search = body.web_search
    if web_search and not perms.get("can_use_web_search"):
        web_search = False

    if body.attachment_ids and not perms.get("can_use_attachments"):
        raise HTTPException(403, "File attachments not enabled for your account.")

    if body.attachment_ids:
        max_att = perms.get("max_attachments_per_message", 5)
        if len(body.attachment_ids) > max_att:
            raise HTTPException(400, f"Too many attachments. Max {max_att} per message.")

    model = check_model_allowed(user.id, body.model or "eu.anthropic.claude-opus-4-6-v1", db)
    max_tokens = min(body.max_tokens, perms.get("max_tokens_cap", 4096))
    reasoning_budget = min(body.reasoning_budget, perms.get("max_reasoning_budget", 0))

    gen_manager.start(
        chat_id=chat_id, user_id=user.id, content=body.content,
        model=model, max_tokens=max_tokens, reasoning_budget=reasoning_budget,
        knowledge_base_id=body.knowledge_base_id,
        attachment_ids=body.attachment_ids,
        web_search=web_search,
    )
    async def generate():
        async for event in gen_manager.stream_events(chat_id):
            yield _sse(event)
    return StreamingResponse(generate(), media_type="text/event-stream")


# ═══════════════════════════════════════════════════
#  GitLab Commit from Chat (FIXED)
# ═══════════════════════════════════════════════════

@router.post("/{chat_id}/commit")
async def commit_from_chat(
    chat_id: str,
    body: CommitFromChatBody,
    user: User = Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """
    Commit files from chat to linked GitLab repo.
    - Auto-detects create vs update per file.
    - Auto-creates branch if it doesn't exist and create_branch_if_missing=True.
    - Returns clear success/failure with details.
    """
    check_feature(user.id, "use_gitlab", db)

    chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
    if not chat:
        raise HTTPException(404, "Chat not found")
    if not chat.linked_repo_id:
        raise HTTPException(400, "No repository linked to this chat")

    repo = db.query(LinkedRepo).filter(LinkedRepo.id == chat.linked_repo_id).first()
    if not repo:
        raise HTTPException(404, "Linked repository not found")

    settings = db.query(GitLabSettings).first()
    if not settings or not settings.is_active:
        raise HTTPException(400, "GitLab not configured. Ask your admin to set it up.")

    branch = body.branch.strip()
    if not branch:
        branch = repo.default_branch or "main"

    # ── Step 1: Check if branch exists, create if needed ──
    branch_exists = True
    try:
        branches = await gitlab_service.list_branches(
            settings.gitlab_url, settings.gitlab_token, repo.gitlab_project_id
        )
        branch_names = {b["name"] for b in branches}
        if branch not in branch_names:
            branch_exists = False
    except Exception:
        # If we can't list branches, we'll try anyway
        pass

    if not branch_exists and body.create_branch_if_missing:
        try:
            await gitlab_service.create_branch(
                settings.gitlab_url, settings.gitlab_token,
                repo.gitlab_project_id, branch, repo.default_branch or "main",
            )
        except gitlab_service.GitLabError as e:
            if "already exists" not in str(e.detail).lower():
                raise HTTPException(e.status_code, f"Could not create branch '{branch}': {e.detail}")

    # ── Step 2: Fetch repo tree to detect create vs update ──
    existing_paths = set()
    try:
        tree = await gitlab_service.get_tree(
            settings.gitlab_url, settings.gitlab_token,
            repo.gitlab_project_id, ref=branch, recursive=True,
        )
        existing_paths = {item["path"] for item in tree if item["type"] == "blob"}
    except Exception:
        # Empty repo or branch just created — all files are "create"
        pass

    # ── Step 3: Build commit actions ──
    actions = []
    committed_files = []
    for f in body.files:
        file_path = f.get("file_path", "").strip()
        content = f.get("content", "")
        if not file_path or not content:
            continue

        file_exists = file_path in existing_paths
        actual_action = "update" if file_exists else "create"

        actions.append({
            "action": actual_action,
            "file_path": file_path,
            "content": content,
        })
        committed_files.append({"file_path": file_path, "action": actual_action})

    if not actions:
        raise HTTPException(400, "No valid files to commit")

    # ── Step 4: Execute commit ──
    try:
        result = await gitlab_service.commit_files(
            settings.gitlab_url, settings.gitlab_token,
            repo.gitlab_project_id, branch, body.commit_message, actions,
        )
        gen_manager.invalidate_repo_cache(repo.id)
        return {
            "ok": True,
            "commit": result,
            "files_committed": len(actions),
            "files": committed_files,
            "branch": branch,
            "branch_created": not branch_exists and body.create_branch_if_missing,
        }
    except gitlab_service.GitLabError as e:
        # Check if it's a "already exists" type error (file was committed despite error)
        error_detail = str(e.detail).lower()
        if "already exists" in error_detail or "already been taken" in error_detail:
            # Files might have been committed — retry with update action
            for a in actions:
                if a["action"] == "create":
                    a["action"] = "update"
            try:
                result = await gitlab_service.commit_files(
                    settings.gitlab_url, settings.gitlab_token,
                    repo.gitlab_project_id, branch, body.commit_message, actions,
                )
                gen_manager.invalidate_repo_cache(repo.id)
                return {
                    "ok": True,
                    "commit": result,
                    "files_committed": len(actions),
                    "files": committed_files,
                    "branch": branch,
                    "retried": True,
                }
            except gitlab_service.GitLabError as e2:
                raise HTTPException(e2.status_code, f"Commit failed on retry: {e2.detail}")
        raise HTTPException(e.status_code, f"Commit failed: {e.detail}")


@router.post("/{chat_id}/refresh-repo")
async def refresh_repo_context(chat_id: str, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
    if not chat or not chat.linked_repo_id:
        raise HTTPException(400, "No repo linked")
    gen_manager.invalidate_repo_cache(chat.linked_repo_id)
    return {"ok": True}


# ═══════════════════════════════════════════════════
#  Helpers
# ═══════════════════════════════════════════════════

def _sse(data):
    return f"data: {json.dumps(data)}\n\n"


def _chat_dict(c, db=None):
    d = {
        "id": c.id, "title": c.title, "model": c.model,
        "knowledge_base_id": c.knowledge_base_id,
        "linked_repo_id": c.linked_repo_id,
        "max_tokens": c.max_tokens or 4096,
        "reasoning_budget": c.reasoning_budget or 0,
        "created_at": str(c.created_at),
        "updated_at": str(c.updated_at),
    }
    if db and c.linked_repo_id:
        repo = db.query(LinkedRepo).filter(LinkedRepo.id == c.linked_repo_id).first()
        if repo:
            d["linked_repo"] = {
                "id": repo.id, "name": repo.name,
                "path_with_namespace": repo.path_with_namespace,
                "default_branch": repo.default_branch,
                "web_url": repo.web_url,
                "gitlab_project_id": repo.gitlab_project_id,
                "map_status": repo.map_status,
            }
    return d


def _msg_dict(m):
    return {
        "id": m.id, "role": m.role, "content": m.content,
        "thinking_content": m.thinking_content,
        "input_tokens": m.input_tokens, "output_tokens": m.output_tokens,
        "created_at": str(m.created_at),
    }


def _att_brief(a):
    return {
        "id": a.id, "original_filename": a.original_filename,
        "mime_type": a.mime_type, "file_type": a.file_type,
        "file_size": a.file_size,
    }


def _repo_brief(r):
    return {
        "id": r.id,
        "gitlab_project_id": r.gitlab_project_id,
        "name": r.name,
        "path_with_namespace": r.path_with_namespace,
        "default_branch": r.default_branch,
        "web_url": r.web_url,
        "description": r.description or "",
        "map_status": r.map_status or "none",
    }