"""Import-batch management (imports.md §K–§L, rebuild edition): the
scope()-filtered batch list with per-batch entry counts (one grouped
query — the legacy N+1 does not port), the post-import summary, and
delete-with-guard.

The delete guard re-keys from the retired "transferred" flag to invoice
attachment (rebuild delta #4 / T-IMP-003): a batch with any entry
attached to an invoice line cannot be deleted, and the count in the
message is the real server-side count — the UI "Locked" label is
cosmetic, this is the guard.
"""

from __future__ import annotations

import uuid

from sqlalchemy import case, delete, func, select
from sqlalchemy.orm import Session

from app.models import ImportBatch, TimeEntry, User
from app.services.authz.policy import can
from app.services.authz.scope import scope
from app.services.imports.errors import ImportsError
from app.services.imports.staging import require_can_manage_imports


def _entry_counts(session: Session, tenant_id: uuid.UUID) -> dict[uuid.UUID, tuple[int, int]]:
    rows = session.execute(
        select(
            TimeEntry.import_id,
            func.count().label("total"),
            func.sum(case((TimeEntry.invoice_line_id.is_not(None), 1), else_=0)).label(
                "applied"
            ),
        )
        .where(TimeEntry.tenant_id == tenant_id, TimeEntry.import_id.is_not(None))
        .group_by(TimeEntry.import_id)
    ).all()
    return {import_id: (total, int(applied or 0)) for import_id, total, applied in rows}


def list_batches(session: Session, actor) -> list[dict]:
    """imports.md B71–B72: newest first, scope()-filtered (view-as of a
    non-owner narrows to the effective user's own batches)."""
    if actor.tenant_id is None:
        return []
    stmt = scope(
        actor,
        "imports:read",
        select(ImportBatch, User.idauth_username).outerjoin(
            User, User.id == ImportBatch.imported_by
        ),
    ).order_by(ImportBatch.imported_at.desc())
    counts = _entry_counts(session, actor.tenant_id)
    out = []
    for batch, importer in session.execute(stmt).all():
        total, applied = counts.get(batch.id, (0, 0))
        out.append(
            {
                "id": str(batch.id),
                "name": batch.name,
                "filename": batch.filename,
                "notes": batch.notes,
                "source": batch.source,
                "row_count": batch.row_count,
                "imported_by": importer,
                "imported_at": batch.imported_at.isoformat() if batch.imported_at else None,
                "total_entries": total,
                "applied_entries": applied,
                "locked": applied > 0,
            }
        )
    return out


def batch_summary(session: Session, actor, batch_id: uuid.UUID) -> dict:
    """The post-import summary card data (imports.md B73): matched vs
    unmatched counts + the distinct unknown emails."""
    batch = session.get(ImportBatch, batch_id)
    if (
        batch is None
        or batch.tenant_id != actor.tenant_id
        or not can(actor, "imports:read", batch)
    ):
        raise ImportsError(404, "BATCH_NOT_FOUND", "Import batch not found.")
    total, matched = session.execute(
        select(
            func.count(),
            func.sum(case((TimeEntry.worker_party_id.is_not(None), 1), else_=0)),
        ).where(TimeEntry.tenant_id == actor.tenant_id, TimeEntry.import_id == batch_id)
    ).one()
    matched = int(matched or 0)
    unknown_emails = [
        email
        for (email,) in session.execute(
            select(TimeEntry.source_user_email)
            .where(
                TimeEntry.tenant_id == actor.tenant_id,
                TimeEntry.import_id == batch_id,
                TimeEntry.worker_party_id.is_(None),
                TimeEntry.source_user_email.is_not(None),
            )
            .distinct()
            .order_by(TimeEntry.source_user_email)
        ).all()
    ]
    return {
        "batch_id": str(batch_id),
        "batch_name": batch.name,
        "total": total,
        "matched": matched,
        "unmatched": total - matched,
        "unknown_emails": unknown_emails,
    }


def delete_batch(session: Session, actor, batch_id: uuid.UUID) -> dict:
    """imports.md B75–B77 with the re-keyed guard: refuse while any
    entry is attached to an invoice line; otherwise delete entries and
    the batch in ONE transaction (no orphan window — T-IMP-028)."""
    require_can_manage_imports(actor, verb="delete batches")
    batch = session.get(ImportBatch, batch_id)
    if batch is None or batch.tenant_id != actor.tenant_id:
        raise ImportsError(404, "BATCH_NOT_FOUND", "Import batch not found.")
    if not can(actor, "imports:read", batch):
        raise ImportsError(
            403,
            "OUT_OF_SCOPE",
            "That batch isn't visible to the user you're viewing as."
            " Exit view-as to make this change.",
        )

    applied = session.scalar(
        select(func.count()).where(
            TimeEntry.tenant_id == actor.tenant_id,
            TimeEntry.import_id == batch_id,
            TimeEntry.invoice_line_id.is_not(None),
        )
    )
    if applied:
        raise ImportsError(
            409,
            "BATCH_LOCKED",
            f"Can't delete: {applied} entries in this batch have already been"
            " applied to an invoice. Detach them from the invoice first.",
        )

    deleted = session.execute(
        delete(TimeEntry).where(
            TimeEntry.tenant_id == actor.tenant_id, TimeEntry.import_id == batch_id
        )
    ).rowcount
    session.delete(batch)
    session.commit()
    return {"deleted_entries": deleted or 0}
