"""AI Copilot service — NL query processing, action execution, RBAC, audit logging."""

from __future__ import annotations

import uuid
from datetime import UTC, datetime
from typing import Any

from sqlalchemy import desc, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.ai.llm_client import get_llm_client
from app.core.audit import log_audit
from app.core.auth import check_permission
from app.models.ai_conversation import AIConversation, AIMessage
from app.models.contact import Contact
from app.models.contact import Contact
from app.models.workflow import Workflow


def _safe_iso(dt) -> str | None:
    if dt is None:
        return None
    try:
        return dt.isoformat() if hasattr(dt, "isoformat") else None
    except Exception:
        return None


def _get_attr(obj, name, default=None):
    try:
        val = getattr(obj, name)
        return val if val is not None else default
    except Exception:
        return default


def _conversation_to_dict(c: AIConversation) -> dict[str, Any]:
    return {
        "id": str(c.id),
        "title": c.title,
        "context": c.context,
        "created_at": _safe_iso(_get_attr(c, "created_at")),
        "updated_at": _safe_iso(_get_attr(c, "updated_at")),
    }


def _message_to_dict(m: AIMessage) -> dict[str, Any]:
    return {
        "id": str(m.id),
        "role": m.role,
        "content": m.content,
        "proposed_actions": m.proposed_actions,
        "executed_action": m.executed_action,
        "execution_result": m.execution_result,
        "created_at": _safe_iso(_get_attr(m, "created_at")),
    }


async def process_query(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    query: str,
    conversation_id: str | None = None,
    context: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Process a natural language query and return proposed actions.

    1. Get or create conversation
    2. Store user message
    3. Call LLM client (mock or real) to get proposed actions
    4. Store assistant message with proposed actions
    5. Return response with conversation_id and proposed_actions
    """
    context = context or {}

    # Get or create conversation
    if conversation_id:
        conv_uuid = uuid.UUID(conversation_id)
        result = await db.execute(
            select(AIConversation).where(
                AIConversation.id == conv_uuid,
                AIConversation.tenant_id == tenant_id,
            )
        )
        conversation = result.scalar_one_or_none()
        if conversation is None:
            return {"error": "Conversation not found", "status_code": 404}
    else:
        conversation = AIConversation(
            tenant_id=tenant_id,
            user_id=user_id,
            title=query[:100] if query else "Untitled",
            context=context,
        )
        db.add(conversation)
        await db.flush()
        await db.refresh(conversation)

    # Get next message index
    count_q = select(func.count()).select_from(
        select(AIMessage).where(AIMessage.conversation_id == conversation.id).subquery()
    )
    count_result = await db.execute(count_q)
    msg_index = count_result.scalar_one()

    # Store user message
    user_msg = AIMessage(
        tenant_id=tenant_id,
        conversation_id=conversation.id,
        role="user",
        content=query,
        message_index=msg_index,
    )
    db.add(user_msg)
    await db.flush()
    await db.refresh(user_msg)

    # Call LLM client
    llm = get_llm_client()
    llm_response = await llm.generate(query, context)

    # Store assistant message
    assistant_msg = AIMessage(
        tenant_id=tenant_id,
        conversation_id=conversation.id,
        role="assistant",
        content=llm_response.message,
        proposed_actions=llm_response.proposed_actions,
        message_index=msg_index + 1,
    )
    db.add(assistant_msg)
    await db.flush()
    await db.refresh(assistant_msg)

    # Log to audit
    await log_audit(
        db,
        tenant_id,
        user_id,
        action="query",
        entity_type="ai_copilot",
        entity_id=conversation.id,
        changes={"query": query, "proposed_action_count": len(llm_response.proposed_actions)},
    )

    return {
        "conversation_id": str(conversation.id),
        "message": llm_response.message,
        "proposed_actions": llm_response.proposed_actions,
    }


async def execute_action(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    role: str,
    conversation_id: str,
    action: dict[str, Any],
) -> dict[str, Any]:
    """Execute a proposed action with RBAC enforcement.

    1. Validate conversation belongs to tenant
    2. Check RBAC permissions for the action
    3. Execute the action (direct DB or API call)
    4. Store execution result in message
    5. Log to audit
    """
    conv_uuid = uuid.UUID(conversation_id)

    # Validate conversation ownership
    result = await db.execute(
        select(AIConversation).where(
            AIConversation.id == conv_uuid,
            AIConversation.tenant_id == tenant_id,
        )
    )
    conversation = result.scalar_one_or_none()
    if conversation is None:
        return {"error": "Conversation not found", "status_code": 404}

    method = action.get("method", "GET").upper()
    path = action.get("path", "")
    body = action.get("body") or {}

    # Determine module and action_type from path for RBAC
    module, action_type = _derive_rbac_from_path(method, path)

    if not check_permission(role, module, action_type):
        return {
            "error": "Insufficient permissions for this action",
            "status_code": 403,
            "success": False,
        }

    # Execute the action
    try:
        exec_result = await _execute_api_action(db, tenant_id, user_id, method, path, body)
    except Exception as exc:
        exec_result = {"error": str(exc), "status_code": 500}

    # Get next message index
    count_q = select(func.count()).select_from(
        select(AIMessage).where(AIMessage.conversation_id == conversation.id).subquery()
    )
    count_result = await db.execute(count_q)
    msg_index = count_result.scalar_one()

    # Store execution message
    exec_msg = AIMessage(
        tenant_id=tenant_id,
        conversation_id=conversation.id,
        role="assistant",
        content=f"Executed {method} {path}",
        executed_action=action,
        execution_result=exec_result,
        message_index=msg_index,
    )
    db.add(exec_msg)
    await db.flush()
    await db.refresh(exec_msg)

    # Log to audit
    await log_audit(
        db,
        tenant_id,
        user_id,
        action="execute",
        entity_type="ai_copilot",
        entity_id=conversation.id,
        changes={"action": action, "result": exec_result},
    )

    return {
        "conversation_id": str(conversation.id),
        "success": exec_result.get("success", True),
        "status_code": exec_result.get("status_code", 200),
        "data": exec_result.get("data"),
        "error": exec_result.get("error"),
    }


async def get_history(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    page: int = 1,
    page_size: int = 20,
) -> dict[str, Any]:
    """Get paginated conversation history for the current user."""
    page = max(1, page)
    page_size = max(1, min(100, page_size))

    base = select(AIConversation).where(
        AIConversation.tenant_id == tenant_id,
        AIConversation.user_id == user_id,
    )

    count_q = select(func.count()).select_from(base.subquery())
    total_result = await db.execute(count_q)
    total = total_result.scalar_one()

    offset = (page - 1) * page_size
    paginated = base.order_by(desc(AIConversation.created_at)).offset(offset).limit(page_size)
    result = await db.execute(paginated)
    conversations = result.scalars().all()

    items: list[dict[str, Any]] = []
    for conv in conversations:
        # Get messages for each conversation
        msg_q = (
            select(AIMessage)
            .where(
                AIMessage.conversation_id == conv.id,
                AIMessage.tenant_id == tenant_id,
            )
            .order_by(AIMessage.message_index)
        )
        msg_result = await db.execute(msg_q)
        messages = msg_result.scalars().all()

        items.append(
            {
                **_conversation_to_dict(conv),
                "messages": [_message_to_dict(m) for m in messages],
            }
        )

    return {
        "items": items,
        "total": total,
        "page": page,
        "page_size": page_size,
    }


async def _execute_api_action(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    method: str,
    path: str,
    body: dict[str, Any],
) -> dict[str, Any]:
    """Execute an API action directly against the database.

    Supports contacts CRUD (including company-type contacts), plus workflow listing.
    """
    # Parse path to determine entity and operation
    parts = path.replace("/api/v1/", "").strip("/").split("/")
    entity = parts[0] if parts else ""
    entity_id = parts[1] if len(parts) > 1 else None

    if entity in ("companies", "contacts"):
        return await _exec_contacts(db, tenant_id, user_id, method, entity_id, body)
    elif entity == "workflows":
        return await _exec_workflows(db, tenant_id, user_id, method, entity_id, body)
    else:
        return {"error": f"Unsupported entity: {entity}", "status_code": 400, "success": False}


async def _exec_contacts(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    method: str,
    entity_id: str | None,
    body: dict[str, Any],
) -> dict[str, Any]:
    """Execute contact operations (unified: company + person)."""
    if method == "GET":
        result = await db.execute(
            select(Contact).where(
                Contact.tenant_id == tenant_id,
                Contact.deleted_at.is_(None),
            )
        )
        contacts = result.scalars().all()
        return {
            "success": True,
            "status_code": 200,
            "data": [{"id": str(c.id), "name": c.name or c.displayname, "email": c.email_1, "type": c.type} for c in contacts],
        }

    elif method == "POST":
        contact = Contact(
            tenant_id=tenant_id,
            type=body.get("type", "company"),
            name=body.get("name", "Untitled"),
            email_1=body.get("email"),
            phone_1=body.get("phone"),
            created_by=user_id,
            updated_by=user_id,
        )
        db.add(contact)
        await db.flush()
        return {
            "success": True,
            "status_code": 201,
            "data": {"id": str(contact.id), "name": contact.name, "type": contact.type},
        }

    return {"error": f"Unsupported method: {method}", "status_code": 400, "success": False}


async def _exec_workflows(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    method: str,
    entity_id: str | None,
    body: dict[str, Any],
) -> dict[str, Any]:
    """Execute workflow operations."""
    if method == "GET":
        result = await db.execute(
            select(Workflow).where(
                Workflow.tenant_id == tenant_id,
                Workflow.is_active.is_(True),
            )
        )
        workflows = result.scalars().all()
        return {
            "success": True,
            "status_code": 200,
            "data": [
                {"id": str(w.id), "name": w.name, "trigger_event": w.trigger_event}
                for w in workflows
            ],
        }

    return {"error": f"Unsupported method: {method}", "status_code": 400, "success": False}


def _derive_rbac_from_path(method: str, path: str) -> tuple[str, str]:
    """Derive module and action_type from HTTP method and path for RBAC.

    Returns (module, action_type) suitable for check_permission().
    """
    parts = path.replace("/api/v1/", "").strip("/").split("/")
    entity = parts[0] if parts else ""

    method_to_action = {
        "GET": "read",
        "POST": "create",
        "PATCH": "update",
        "DELETE": "delete",
    }
    action_type = method_to_action.get(method, "read")

    # Map path entities to permission modules
    entity_to_module = {
        "companies": "companies",
        "contacts": "contacts",
        "workflows": "workflows",
        "ai": "ai_copilot",
    }
    module = entity_to_module.get(entity, entity)

    return module, action_type
