/**
 * Admin enquiry orchestration (PLANNING.md §4b). Controllers stay HTTP-only.
 * Mirrors the media-admin service: repo rows in, DTOs out, `fromSqlUtc` for
 * timestamps, batched related lookups, `db.transaction` for the multi-table
 * reply write.
 */
import { AppError } from '../../lib/AppError';
import { env } from '../../env';
import { logger } from '../../lib/logger';
import { fromSqlUtc } from '../../lib/dates';
import { renderTemplate, sendMail } from '../../lib/mailer';
import { enquiryReplyTemplate } from '../../emails/enquiryReply';
import type { ListQuery } from './enquiry.schema';
import {
  countEnquiries,
  db,
  findEnquiryById,
  insertReply,
  listEnquiries,
  listRepliesWithAuthor,
  markEnquiryRead,
  replyCounts,
  updateEnquiryStatus,
  updateReplyDelivery,
  type EnquiryRow,
  type EnquiryStatus,
  type ReplyWithAuthor,
} from './enquiry.repository';

const REPLY_SUBJECT = 'Re: your enquiry to Genesis Coworking Space';

export interface EnquiryListItem {
  id: string;
  name: string;
  email: string;
  phone: string;
  message: string;
  source: EnquiryRow['source'];
  status: EnquiryStatus;
  spamScore: number;
  spamReason: string | null;
  replyCount: number;
  notifiedAt: string | null;
  createdAt: string;
  updatedAt: string;
}

export interface EnquiryReplyDto {
  id: string;
  body: string;
  author: { id: string; name: string | null };
  deliveryStatus: 'pending' | 'sent' | 'failed';
  deliveryError: string | null;
  sentAt: string | null;
  createdAt: string;
}

export interface EnquiryDetail extends EnquiryListItem {
  userAgent: string | null;
  referer: string | null;
  ipHash: string | null;
  replies: EnquiryReplyDto[];
}

const iso = (s: string | null): string | null => (s ? fromSqlUtc(s).toISOString() : null);

function toListItem(row: EnquiryRow, replyCount: number): EnquiryListItem {
  return {
    id: row.id,
    name: row.name,
    email: row.email,
    phone: row.phone,
    message: row.message,
    source: row.source,
    status: row.status,
    spamScore: row.spam_score,
    spamReason: row.spam_reason,
    replyCount,
    notifiedAt: iso(row.notified_at),
    createdAt: fromSqlUtc(row.created_at).toISOString(),
    updatedAt: fromSqlUtc(row.updated_at).toISOString(),
  };
}

function toReplyDto(r: ReplyWithAuthor): EnquiryReplyDto {
  return {
    id: r.id,
    body: r.body,
    author: { id: r.admin_user_id, name: r.author_name },
    deliveryStatus: r.delivery_status,
    deliveryError: r.delivery_error,
    sentAt: iso(r.sent_at),
    createdAt: fromSqlUtc(r.created_at).toISOString(),
  };
}

async function requireEnquiry(id: string): Promise<EnquiryRow> {
  const row = await findEnquiryById(id);
  if (!row) throw AppError.notFound('Enquiry not found');
  return row;
}

async function buildDetail(row: EnquiryRow): Promise<EnquiryDetail> {
  const replies = await listRepliesWithAuthor(row.id);
  const base = toListItem(row, replies.length);
  return {
    ...base,
    userAgent: row.user_agent,
    referer: row.referer,
    ipHash: row.ip_hash,
    replies: replies.map(toReplyDto),
  };
}

// ─── endpoints ─────────────────────────────────────────────────────────────

export async function list(query: ListQuery): Promise<{
  data: EnquiryListItem[];
  meta: { page: number; perPage: number; total: number };
}> {
  const filter = {
    status: query.status,
    source: query.source,
    q: query.q,
    from: query.from,
    to: query.to,
  };
  const offset = (query.page - 1) * query.perPage;

  const [rows, total] = await Promise.all([
    listEnquiries(filter, { limit: query.perPage, offset, sort: query.sort }),
    countEnquiries(filter),
  ]);
  const counts = await replyCounts(rows.map((r) => r.id));

  return {
    data: rows.map((r) => toListItem(r, counts.get(r.id) ?? 0)),
    meta: { page: query.page, perPage: query.perPage, total },
  };
}

export async function getById(id: string): Promise<EnquiryDetail> {
  const row = await requireEnquiry(id);
  if (row.status === 'new') {
    await markEnquiryRead(id); // §4b — first open marks it read
    row.status = 'read';
  }
  return buildDetail(row);
}

export async function setStatus(id: string, status: EnquiryStatus): Promise<EnquiryDetail> {
  await requireEnquiry(id);
  await updateEnquiryStatus(id, status);
  return buildDetail(await requireEnquiry(id));
}

export async function reply(id: string, adminId: string, body: string): Promise<EnquiryDetail> {
  const enquiry = await requireEnquiry(id);

  const replyId = await db.transaction(async (trx) => {
    const rid = await insertReply({ enquiry_id: id, admin_user_id: adminId, body }, trx);
    await updateEnquiryStatus(id, 'replied', trx); // the admin engaged, even from spam/archived
    return rid;
  });

  // Delivery outcome is recorded on the row; a bounce never fails the request
  // (PLANNING.md §9 — the reply is durably stored).
  try {
    const { skipped } = await sendMail({
      to: enquiry.email,
      subject: REPLY_SUBJECT,
      html: renderTemplate(enquiryReplyTemplate, {
        name: enquiry.name,
        replyBody: body,
        originalMessage: enquiry.message,
      }),
      replyTo: env.ENQUIRY_NOTIFY_TO,
    });
    await updateReplyDelivery(replyId, { delivery_status: 'sent', markSent: true });
    if (skipped) logger.warn({ enquiryId: id, replyId }, 'reply stored but SMTP not configured');
  } catch (err) {
    await updateReplyDelivery(replyId, {
      delivery_status: 'failed',
      delivery_error: (err instanceof Error ? err.message : String(err)).slice(0, 255),
    });
    logger.error({ err, enquiryId: id, replyId }, 'reply email failed');
  }

  return buildDetail(await requireEnquiry(id));
}
