import base64
import json
from fastapi import APIRouter, Depends, HTTPException, Query, Form, File, UploadFile
from pydantic import BaseModel
from typing import List, Optional

from auth.models import TokenPayload
from auth.dependencies import require_permission
from config import settings
from crm.models import CommCreate, CommUpdate, CommInDB, CommListResponse, MediaCreate, MediaDirection
from crm import service
from crm import email_sync
from crm.mail_accounts import get_mail_accounts

router = APIRouter(prefix="/api/crm/comms", tags=["crm-comms"])


class EmailSendResponse(BaseModel):
    entry: dict


class EmailSyncResponse(BaseModel):
    new_count: int


class MailListResponse(BaseModel):
    entries: list
    total: int


@router.get("/latest-batch", response_model=dict)
async def latest_comm_batch(
    ids: str = Query(..., description="Comma-separated customer IDs"),
    _user: TokenPayload = Depends(require_permission("crm", "view")),
):
    """Return the latest comm summary (id, type, occurred_at) keyed by customer_id."""
    customer_ids = [i.strip() for i in ids.split(",") if i.strip()]
    return await service.get_latest_comm_batch(customer_ids)


@router.get("/all", response_model=CommListResponse)
async def list_all_comms(
    type: Optional[str] = Query(None),
    direction: Optional[str] = Query(None),
    limit: int = Query(200, le=500),
    _user: TokenPayload = Depends(require_permission("crm", "view")),
):
    entries = await service.list_all_comms(type=type, direction=direction, limit=limit)
    return CommListResponse(entries=entries, total=len(entries))


@router.get("", response_model=CommListResponse)
async def list_comms(
    customer_id: str = Query(...),
    type: Optional[str] = Query(None),
    direction: Optional[str] = Query(None),
    _user: TokenPayload = Depends(require_permission("crm", "view")),
):
    entries = await service.list_comms(customer_id=customer_id, type=type, direction=direction)
    return CommListResponse(entries=entries, total=len(entries))


@router.post("", response_model=CommInDB, status_code=201)
async def create_comm(
    body: CommCreate,
    _user: TokenPayload = Depends(require_permission("crm", "edit")),
):
    return await service.create_comm(body)


@router.get("/email/all", response_model=MailListResponse)
async def list_all_emails(
    direction: Optional[str] = Query(None),
    customers_only: bool = Query(False),
    mailbox: Optional[str] = Query(None, description="sales|support|both|all or account key"),
    limit: int = Query(500, le=1000),
    _user: TokenPayload = Depends(require_permission("crm", "view")),
):
    """Return all email comms (all senders + unmatched), for the Mail page."""
    selected_accounts = None
    if mailbox and mailbox not in {"all", "both"}:
      if mailbox == "sales":
          selected_accounts = ["sales"]
      elif mailbox == "support":
          selected_accounts = ["support"]
      else:
          selected_accounts = [mailbox]
    entries = await service.list_all_emails(
        direction=direction,
        customers_only=customers_only,
        mail_accounts=selected_accounts,
        limit=limit,
    )
    return MailListResponse(entries=entries, total=len(entries))


@router.get("/email/accounts")
async def list_mail_accounts(
    _user: TokenPayload = Depends(require_permission("crm", "view")),
):
    accounts = get_mail_accounts()
    return {
        "accounts": [
            {
                "key": a["key"],
                "label": a["label"],
                "email": a["email"],
                "sync_inbound": bool(a.get("sync_inbound")),
                "allow_send": bool(a.get("allow_send")),
            }
            for a in accounts
        ]
    }


@router.get("/email/check")
async def check_new_emails(
    _user: TokenPayload = Depends(require_permission("crm", "view")),
):
    """Lightweight check: returns how many emails are on the server vs. stored locally."""
    return await email_sync.check_new_emails()


# Email endpoints — must be before /{comm_id} wildcard routes
@router.post("/email/send", response_model=EmailSendResponse)
async def send_email_endpoint(
    customer_id: Optional[str] = Form(None),
    from_account: Optional[str] = Form(None),
    to: str = Form(...),
    subject: str = Form(...),
    body: str = Form(...),
    body_html: str = Form(""),
    cc: str = Form("[]"),  # JSON-encoded list of strings
    files: List[UploadFile] = File(default=[]),
    user: TokenPayload = Depends(require_permission("crm", "edit")),
):
    if not get_mail_accounts():
        raise HTTPException(status_code=503, detail="SMTP not configured")
    try:
        cc_list: List[str] = json.loads(cc) if cc else []
    except Exception:
        cc_list = []

    # Read all uploaded files into memory
    file_attachments = []
    for f in files:
        content = await f.read()
        mime_type = f.content_type or "application/octet-stream"
        file_attachments.append((f.filename, content, mime_type))

    from crm.email_sync import send_email
    try:
        entry = await send_email(
            customer_id=customer_id or None,
            from_account=from_account,
            to=to,
            subject=subject,
            body=body,
            body_html=body_html,
            cc=cc_list,
            sent_by=user.name or user.sub,
            file_attachments=file_attachments if file_attachments else None,
        )
    except RuntimeError as e:
        raise HTTPException(status_code=400, detail=str(e))
    return EmailSendResponse(entry=entry)


@router.post("/email/sync", response_model=EmailSyncResponse)
async def sync_email_endpoint(
    _user: TokenPayload = Depends(require_permission("crm", "edit")),
):
    if not get_mail_accounts():
        raise HTTPException(status_code=503, detail="IMAP not configured")
    from crm.email_sync import sync_emails
    new_count = await sync_emails()
    return EmailSyncResponse(new_count=new_count)


class SaveInlineRequest(BaseModel):
    data_uri: str
    filename: str
    subfolder: str = "received_media"
    mime_type: Optional[str] = None


async def _resolve_customer_folder(customer_id: str) -> str:
    """Return the Nextcloud folder_id for a customer (falls back to customer_id)."""
    from shared.firebase import get_db as get_firestore
    firestore_db = get_firestore()
    doc = firestore_db.collection("crm_customers").document(customer_id).get()
    if not doc.exists:
        raise HTTPException(status_code=404, detail="Customer not found")
    data = doc.to_dict()
    return data.get("folder_id") or customer_id


async def _upload_to_nc(folder_id: str, subfolder: str, filename: str,
                        content: bytes, mime_type: str, customer_id: str,
                        uploaded_by: str, tags: list[str]) -> dict:
    from crm import nextcloud
    target_folder = f"customers/{folder_id}/{subfolder}"
    file_path = f"{target_folder}/{filename}"
    await nextcloud.ensure_folder(target_folder)
    await nextcloud.upload_file(file_path, content, mime_type)
    media = await service.create_media(MediaCreate(
        customer_id=customer_id,
        filename=filename,
        nextcloud_path=file_path,
        mime_type=mime_type,
        direction=MediaDirection.received,
        tags=tags,
        uploaded_by=uploaded_by,
    ))
    return {"ok": True, "media_id": media.id, "nextcloud_path": file_path}


@router.post("/email/{comm_id}/save-inline")
async def save_email_inline_image(
    comm_id: str,
    body: SaveInlineRequest,
    user: TokenPayload = Depends(require_permission("crm", "edit")),
):
    """Save an inline image (data-URI from email HTML body) to Nextcloud."""
    comm = await service.get_comm(comm_id)
    customer_id = comm.customer_id
    if not customer_id:
        raise HTTPException(status_code=400, detail="This email is not linked to a customer")

    folder_id = await _resolve_customer_folder(customer_id)

    # Parse data URI
    data_uri = body.data_uri
    mime_type = body.mime_type or "image/png"
    if "," in data_uri:
        header, encoded = data_uri.split(",", 1)
        try:
            mime_type = header.split(":")[1].split(";")[0]
        except Exception:
            pass
    else:
        encoded = data_uri
    try:
        content = base64.b64decode(encoded)
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid base64 data")

    return await _upload_to_nc(
        folder_id, body.subfolder, body.filename,
        content, mime_type, customer_id,
        user.name or user.sub, ["email-inline-image"],
    )


@router.post("/email/{comm_id}/save-attachment/{attachment_index}")
async def save_email_attachment(
    comm_id: str,
    attachment_index: int,
    filename: str = Form(...),
    subfolder: str = Form("received_media"),
    user: TokenPayload = Depends(require_permission("crm", "edit")),
):
    """
    Re-fetch a specific attachment from IMAP (by index in the email's attachment list)
    and save it to the customer's Nextcloud media folder.
    """
    import asyncio
    comm = await service.get_comm(comm_id)
    customer_id = comm.customer_id
    if not customer_id:
        raise HTTPException(status_code=400, detail="This email is not linked to a customer")

    ext_message_id = comm.ext_message_id
    if not ext_message_id:
        raise HTTPException(status_code=400, detail="No message ID stored for this email")

    attachments_meta = comm.attachments or []
    if attachment_index < 0 or attachment_index >= len(attachments_meta):
        raise HTTPException(status_code=400, detail="Attachment index out of range")

    att_meta = attachments_meta[attachment_index]
    mime_type = att_meta.content_type or "application/octet-stream"
    from crm.mail_accounts import account_by_key, account_by_email
    account = account_by_key(comm.mail_account) or account_by_email(comm.from_addr)
    if not account:
        raise HTTPException(status_code=400, detail="Email account config not found for this message")

    # Re-fetch from IMAP in executor
    def _fetch_attachment():
        import imaplib, email as _email
        if account.get("imap_use_ssl"):
            imap = imaplib.IMAP4_SSL(account["imap_host"], int(account["imap_port"]))
        else:
            imap = imaplib.IMAP4(account["imap_host"], int(account["imap_port"]))
        imap.login(account["imap_username"], account["imap_password"])
        imap.select(account.get("imap_inbox", "INBOX"))

        # Search by Message-ID header
        _, data = imap.search(None, f'HEADER Message-ID "{ext_message_id}"')
        uids = data[0].split() if data[0] else []
        if not uids:
            raise ValueError(f"Message not found on IMAP server: {ext_message_id}")

        _, msg_data = imap.fetch(uids[0], "(RFC822)")
        raw = msg_data[0][1]
        msg = _email.message_from_bytes(raw)
        imap.logout()

        # Walk attachments in order — find the one at attachment_index
        found_idx = 0
        for part in msg.walk():
            cd = str(part.get("Content-Disposition", ""))
            if "attachment" not in cd:
                continue
            if found_idx == attachment_index:
                payload = part.get_payload(decode=True)
                if payload is None:
                    raise ValueError("Attachment payload is empty")
                return payload
            found_idx += 1

        raise ValueError(f"Attachment index {attachment_index} not found in message")

    loop = asyncio.get_event_loop()
    try:
        content = await loop.run_in_executor(None, _fetch_attachment)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=502, detail=f"IMAP fetch failed: {e}")

    folder_id = await _resolve_customer_folder(customer_id)
    return await _upload_to_nc(
        folder_id, subfolder, filename,
        content, mime_type, customer_id,
        user.name or user.sub, ["email-attachment"],
    )


class BulkDeleteRequest(BaseModel):
    ids: List[str]


class ToggleImportantRequest(BaseModel):
    important: bool


class ToggleReadRequest(BaseModel):
    read: bool


@router.post("/bulk-delete", status_code=200)
async def bulk_delete_comms(
    body: BulkDeleteRequest,
    _user: TokenPayload = Depends(require_permission("crm", "edit")),
):
    # Try remote IMAP delete for email rows first (best-effort), then local delete.
    for comm_id in body.ids:
        try:
            comm = await service.get_comm(comm_id)
            if comm.type == "email" and comm.ext_message_id:
                await email_sync.delete_remote_email(
                    comm.ext_message_id,
                    comm.mail_account,
                    comm.from_addr,
                )
        except Exception:
            # Keep delete resilient; local delete still proceeds.
            pass
    count = await service.delete_comms_bulk(body.ids)
    return {"deleted": count}


@router.patch("/{comm_id}/important", response_model=CommInDB)
async def set_comm_important(
    comm_id: str,
    body: ToggleImportantRequest,
    _user: TokenPayload = Depends(require_permission("crm", "edit")),
):
    return await service.set_comm_important(comm_id, body.important)


@router.patch("/{comm_id}/read", response_model=CommInDB)
async def set_comm_read(
    comm_id: str,
    body: ToggleReadRequest,
    _user: TokenPayload = Depends(require_permission("crm", "edit")),
):
    try:
        comm = await service.get_comm(comm_id)
        if comm.type == "email" and comm.ext_message_id:
            await email_sync.set_remote_read(
                comm.ext_message_id,
                comm.mail_account,
                comm.from_addr,
                body.read,
            )
    except Exception:
        pass
    return await service.set_comm_read(comm_id, body.read)


@router.put("/{comm_id}", response_model=CommInDB)
async def update_comm(
    comm_id: str,
    body: CommUpdate,
    _user: TokenPayload = Depends(require_permission("crm", "edit")),
):
    return await service.update_comm(comm_id, body)


@router.delete("/{comm_id}", status_code=204)
async def delete_comm(
    comm_id: str,
    _user: TokenPayload = Depends(require_permission("crm", "edit")),
):
    try:
        comm = await service.get_comm(comm_id)
        if comm.type == "email" and comm.ext_message_id:
            await email_sync.delete_remote_email(
                comm.ext_message_id,
                comm.mail_account,
                comm.from_addr,
            )
    except Exception:
        pass
    await service.delete_comm(comm_id)
