"""Contact service — CRUD, N:M linking, soft-delete, GDPR hard-delete."""

from __future__ import annotations

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

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

from app.core.audit import log_audit, log_deletion
from app.models.company import Company
from app.models.contact import CompanyContact, Contact


def _contact_to_dict(c: Contact, include_companies: bool = False) -> dict[str, Any]:
    """Serialize a Contact ORM object to dict."""
    data: dict[str, Any] = {
        "id": str(c.id),
        "first_name": c.first_name,
        "last_name": c.last_name,
        "email": c.email,
        "phone": c.phone,
        "mobile": c.mobile,
        "position": c.position,
        "department": c.department,
        "linkedin_url": c.linkedin_url,
        "notes": c.notes,
        "created_at": c.created_at.isoformat() if c.created_at else None,
        "updated_at": c.updated_at.isoformat() if c.updated_at else None,
    }
    if include_companies:
        data["companies"] = []
    return data


def _filter_contact_fields(data: dict, resolved_perms: dict) -> dict:
    """Filter contact fields based on field-level permissions."""
    from app.core.permissions import filter_fields_by_permission
    return filter_fields_by_permission(data, resolved_perms, "contacts")


async def list_contacts(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    page: int = 1,
    page_size: int = 20,
    search: str | None = None,
    sort_by: str = "last_name",
    sort_order: str = "asc",
    resolved_perms: dict | None = None,
) -> dict[str, Any]:
    """List contacts with pagination and optional search."""
    page = max(1, page)
    page_size = max(1, min(100, page_size))

    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))
        )

    sort_col = getattr(Contact, sort_by, Contact.last_name)
    if sort_order == "desc":
        base = base.order_by(desc(sort_col))
    else:
        base = base.order_by(asc(sort_col))

    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.offset(offset).limit(page_size)
    result = await db.execute(paginated)
    contacts = result.scalars().all()

    items = [_contact_to_dict(c) for c in contacts]
    if resolved_perms is not None:
        items = [_filter_contact_fields(item, resolved_perms) for item in items]

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


async def get_contact_detail(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    contact_id: uuid.UUID,
    resolved_perms: dict | None = None,
) -> dict[str, Any] | None:
    """Get a single contact with its linked companies array."""
    q = select(Contact).where(
        Contact.id == contact_id,
        Contact.tenant_id == tenant_id,
        Contact.deleted_at.is_(None),
    )
    result = await db.execute(q)
    contact = result.scalar_one_or_none()
    if contact is None:
        return None

    data = _contact_to_dict(contact, include_companies=True)
    companies_q = (
        select(Company, CompanyContact)
        .join(CompanyContact, CompanyContact.company_id == Company.id)
        .where(
            CompanyContact.contact_id == contact_id,
            CompanyContact.tenant_id == tenant_id,
            Company.deleted_at.is_(None),
        )
    )
    companies_result = await db.execute(companies_q)
    companies_list = []
    for company, link in companies_result.all():
        companies_list.append(
            {
                "id": str(company.id),
                "name": company.name,
                "industry": company.industry,
                "role_at_company": link.role_at_company,
                "is_primary": link.is_primary,
            }
        )
    data["companies"] = companies_list
    if resolved_perms is not None:
        data = _filter_contact_fields(data, resolved_perms)
    return data


async def create_contact(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    data: dict[str, Any],
) -> dict[str, Any]:
    """Create a new contact, optionally linking to companies via company_ids."""
    contact = Contact(
        tenant_id=tenant_id,
        first_name=data["first_name"],
        last_name=data["last_name"],
        email=data.get("email"),
        phone=data.get("phone"),
        mobile=data.get("mobile"),
        position=data.get("position"),
        department=data.get("department"),
        linkedin_url=data.get("linkedin_url"),
        notes=data.get("notes"),
        created_by=user_id,
        updated_by=user_id,
    )
    db.add(contact)
    await db.flush()

    company_ids = data.get("company_ids")
    linked_companies = []
    if company_ids:
        for cid_str in company_ids:
            try:
                cid = uuid.UUID(cid_str) if isinstance(cid_str, str) else cid_str
            except (ValueError, TypeError):
                continue
            comp_q = select(Company).where(
                Company.id == cid,
                Company.tenant_id == tenant_id,
                Company.deleted_at.is_(None),
            )
            comp_result = await db.execute(comp_q)
            if comp_result.scalar_one_or_none() is None:
                continue
            link = CompanyContact(
                tenant_id=tenant_id,
                company_id=cid,
                contact_id=contact.id,
            )
            db.add(link)
            linked_companies.append(str(cid))
        await db.flush()

    await db.refresh(contact)
    await log_audit(
        db,
        tenant_id,
        user_id,
        "create",
        "contact",
        contact.id,
        changes={
            "first_name": data["first_name"],
            "last_name": data["last_name"],
            "linked_companies": linked_companies,
        },
    )
    return _contact_to_dict(contact)


async def update_contact(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    contact_id: uuid.UUID,
    data: dict[str, Any],
) -> dict[str, Any] | None:
    """Update a contact (partial update) and audit-log changes."""
    q = select(Contact).where(
        Contact.id == contact_id,
        Contact.tenant_id == tenant_id,
        Contact.deleted_at.is_(None),
    )
    result = await db.execute(q)
    contact = result.scalar_one_or_none()
    if contact is None:
        return None

    changes: dict[str, Any] = {}
    all_fields = (
        "first_name", "last_name", "email", "phone", "mobile",
        "position", "department", "linkedin_url", "notes",
    )
    for field in all_fields:
        if field in data and data[field] is not None:
            old_val = getattr(contact, field)
            changes[field] = {"old": old_val, "new": data[field]}
            setattr(contact, field, data[field])
    contact.updated_by = user_id

    await db.flush()
    await db.refresh(contact)
    await log_audit(db, tenant_id, user_id, "update", "contact", contact_id, changes=changes)
    return _contact_to_dict(contact)


async def soft_delete_contact(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    contact_id: uuid.UUID,
) -> bool:
    """Soft-delete a contact (set deleted_at)."""
    q = select(Contact).where(
        Contact.id == contact_id,
        Contact.tenant_id == tenant_id,
        Contact.deleted_at.is_(None),
    )
    result = await db.execute(q)
    contact = result.scalar_one_or_none()
    if contact is None:
        return False

    contact.deleted_at = datetime.now(UTC)
    contact.updated_by = user_id

    await db.flush()
    await log_audit(
        db, tenant_id, user_id, "delete", "contact", contact_id,
        changes={"first_name": contact.first_name, "last_name": contact.last_name},
    )
    return True


async def gdpr_hard_delete_contact(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    contact_id: uuid.UUID,
) -> bool:
    """GDPR hard-delete: physical delete + deletion_log entry with snapshot."""
    q = select(Contact).where(
        Contact.id == contact_id,
        Contact.tenant_id == tenant_id,
    )
    result = await db.execute(q)
    contact = result.scalar_one_or_none()
    if contact is None:
        return False

    snapshot = _contact_to_dict(contact)

    await db.execute(
        delete(CompanyContact).where(
            CompanyContact.contact_id == contact_id,
            CompanyContact.tenant_id == tenant_id,
        )
    )

    await db.delete(contact)
    await db.flush()

    await log_deletion(
        db,
        tenant_id,
        user_id,
        "contact",
        contact_id,
        snapshot,
    )
    return True
