"""Contact routes — CRUD, N:M, soft-delete, GDPR hard-delete, streaming CSV export."""

from __future__ import annotations

import csv
import io
import uuid

from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from fastapi.responses import StreamingResponse
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.db import get_db
from app.deps import require_permission
from app.models.contact import Contact
from app.schemas.contact import ContactCreate, ContactUpdate
from app.services import contact_service

router = APIRouter(prefix="/api/v1/contacts", tags=["contacts"])


@router.get("")
async def list_contacts(
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100),
    search: str | None = Query(None),
    sort_by: str = Query("last_name"),
    sort_order: str = Query("asc", pattern="^(asc|desc)$"),
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("contacts:read")),
):
    """List contacts with pagination and optional search.

    page_size is capped at 100 — values >100 return 422.
    """
    tenant_id = uuid.UUID(current_user["tenant_id"])
    result = await contact_service.list_contacts(
        db,
        tenant_id,
        page=page,
        page_size=page_size,
        search=search,
        sort_by=sort_by,
        sort_order=sort_order,
        resolved_perms=current_user,
    )
    return result


@router.get("/export")
async def export_contacts(
    format: str = Query("csv", pattern="^(csv)$"),
    search: str | None = Query(None),
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("contacts:read")),
):
    """Stream contacts as CSV (not buffered — uses StreamingResponse).

    For large exports (>1000 records), an ARQ background job should be started
    with a notification on completion. This endpoint streams directly for
    immediate download.
    """
    tenant_id = uuid.UUID(current_user["tenant_id"])
    from app.core.db import get_session_factory

    async def generate_csv():
        """Async generator yielding CSV rows one at a time (streaming).
        Creates its own DB session to avoid using the request-scoped session
        which gets closed after the endpoint function returns.
        """
        output = io.StringIO()
        writer = csv.writer(output)

        # Write header row
        header = [
            "id",
            "first_name",
            "last_name",
            "email",
            "phone",
            "mobile",
            "position",
            "department",
            "linkedin_url",
            "notes",
            "created_at",
            "updated_at",
        ]
        writer.writerow(header)
        yield output.getvalue()
        output.seek(0)
        output.truncate(0)

        # Stream rows in batches to avoid loading all into memory
        batch_size = 500
        offset = 0
        factory = get_session_factory()
        async with factory() as session:
            while True:
                base = select(Contact).where(
                    Contact.tenant_id == tenant_id,
                    Contact.deleted_at.is_(None),
                )
                if search:
                    pattern = f"%{search}%"
                    base = base.where(
                        (Contact.first_name.ilike(pattern))
                        | (Contact.last_name.ilike(pattern))
                        | (Contact.email.ilike(pattern))
                    )
                base = base.order_by(Contact.last_name.asc()).offset(offset).limit(batch_size)
                result = await session.execute(base)
                contacts = result.scalars().all()
                if not contacts:
                    break

                for c in contacts:
                    writer.writerow(
                        [
                            str(c.id),
                            c.first_name,
                            c.last_name,
                            c.email or "",
                            c.phone or "",
                            c.mobile or "",
                            c.position or "",
                            c.department or "",
                            c.linkedin_url or "",
                            c.notes or "",
                            c.created_at.isoformat() if c.created_at else "",
                            c.updated_at.isoformat() if c.updated_at else "",
                        ]
                    )
                yield output.getvalue()
                output.seek(0)
                output.truncate(0)
                offset += batch_size

    return StreamingResponse(
        generate_csv(),
        media_type="text/csv",
        headers={"Content-Disposition": "attachment; filename=contacts.csv"},
    )


@router.post("", status_code=status.HTTP_201_CREATED)
async def create_contact(
    body: ContactCreate,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("contacts:write")),
):
    """Create a contact. Optionally link to companies via company_ids array."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    user_id = uuid.UUID(current_user["user_id"])


    data = body.model_dump()
    return await contact_service.create_contact(db, tenant_id, user_id, data)


@router.get("/{contact_id}")
async def get_contact(
    contact_id: str,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("contacts:read")),
):
    """Get a single contact with companies array. Cross-tenant returns 404."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    try:
        cid = uuid.UUID(contact_id)
    except ValueError:
        raise HTTPException(
            400, detail={"detail": "Invalid contact_id", "code": "invalid_id"}
        ) from None

    data = await contact_service.get_contact_detail(db, tenant_id, cid, resolved_perms=current_user)
    if data is None:
        raise HTTPException(404, detail={"detail": "Contact not found", "code": "not_found"})
    return data


@router.put("/{contact_id}")
async def update_contact(
    contact_id: str,
    body: ContactUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("contacts:write")),
):
    """Update a contact."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    user_id = uuid.UUID(current_user["user_id"])


    try:
        cid = uuid.UUID(contact_id)
    except ValueError:
        raise HTTPException(
            400, detail={"detail": "Invalid contact_id", "code": "invalid_id"}
        ) from None

    data = body.model_dump(exclude_unset=True)
    result = await contact_service.update_contact(db, tenant_id, user_id, cid, data)
    if result is None:
        raise HTTPException(404, detail={"detail": "Contact not found", "code": "not_found"})
    return result


@router.delete("/{contact_id}")
async def delete_contact(
    contact_id: str,
    gdpr: bool = Query(False),
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("contacts:write")),
):
    """Delete a contact. Default: soft-delete. Use gdpr=true for hard-delete with deletion_log."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    user_id = uuid.UUID(current_user["user_id"])


    try:
        cid = uuid.UUID(contact_id)
    except ValueError:
        raise HTTPException(
            400, detail={"detail": "Invalid contact_id", "code": "invalid_id"}
        ) from None

    if gdpr:
        deleted = await contact_service.gdpr_hard_delete_contact(db, tenant_id, user_id, cid)
    else:
        deleted = await contact_service.soft_delete_contact(db, tenant_id, user_id, cid)

    if not deleted:
        raise HTTPException(404, detail={"detail": "Contact not found", "code": "not_found"})

    return Response(status_code=status.HTTP_204_NO_CONTENT)
