/**
 * Admin vendor resource — write actions.
 *
 * - setVendorTier: set vendor tier (NEW/VETERAN) in both directions.
 * - activateVendor: PENDING/PENDING_PROCESSOR -> ACTIVE.
 * - freezeVendor: delegates to violation workflow via dynamic import.
 * - unfreezeVendor: restore to ACTIVE/VETERAN based on tier.
 * - banVendor: permanent ban + policy-violation email.
 * - sendMessageToVendor: in-app via outbox + push + email.
 * - approveAnywayVendor: admin override of AI rejection.
 *
 * All actions write to admin_actions audit table. PII never logged.
 */

import { eq } from 'drizzle-orm';
import { invalidateCatalog } from '@/server/cache/invalidate.js';
import { vendors } from '@/server/db/schema.js';
import { sendPolicyViolation } from '@/server/services/email';
import { captureCaught } from '@/server/observability/capture.server';
import { VendorManagementError } from './errors.js';
import type {
  VendorManagementDeps,
  SetVendorTierInput,
  ActivateVendorInput,
  FreezeVendorInput,
  UnfreezeVendorInput,
  BanVendorInput,
  SendVendorMessageInput,
  ApproveAnywayInput,
} from './types.js';
import { bumpMhVersion } from '@/server/auth/session.js';
import { recordAdminAction } from '@/server/db/queries/admin-actions.js';
import {
  approveVendorAnyway,
  findById as findVendorById,
  setVendorAccountState,
  setVendorTierAndAccountState,
} from '@/server/db/queries/vendors.js';

// ─── setVendorTier ────────────────────────────────────────────────────────────

export async function setVendorTier(
  deps: VendorManagementDeps,
  input: SetVendorTierInput,
): Promise<void> {
  const { db } = deps;
  const { adminId, vendorId, tier, note } = input;

  const [vendor] = await db
    .select({ id: vendors.id, accountState: vendors.accountState, tier: vendors.tier })
    .from(vendors)
    .where(eq(vendors.id, vendorId))
    .limit(1);

  if (!vendor) {
    throw new VendorManagementError('VENDOR_NOT_FOUND', 'Vendor not found');
  }

  if (!['ACTIVE', 'VETERAN'].includes(vendor.accountState)) {
    throw new VendorManagementError(
      'INVALID_STATE',
      `Cannot change tier for vendor in state ${vendor.accountState}`,
    );
  }

  if (vendor.tier === tier) return; // no-op

  const nextAccountState = tier === 'VETERAN' ? 'VETERAN' : 'ACTIVE';

  await setVendorTierAndAccountState(db, vendorId, tier, nextAccountState);

  await recordAdminAction(db, {
    adminId,
    targetType: 'VENDOR',
    targetId: vendorId,
    action: 'APPROVE',
    note: note ?? (tier === 'VETERAN' ? 'Promoted to VETERAN' : 'Demoted to NEW'),
  });
}

// ─── activateVendor ───────────────────────────────────────────────────────────

export async function activateVendor(
  deps: VendorManagementDeps,
  input: ActivateVendorInput,
): Promise<void> {
  const { db } = deps;
  const { adminId, vendorId, note } = input;

  const [vendor] = await db
    .select({
      id: vendors.id,
      accountState: vendors.accountState,
      ownerUserId: vendors.ownerUserId,
    })
    .from(vendors)
    .where(eq(vendors.id, vendorId))
    .limit(1);

  if (!vendor) {
    throw new VendorManagementError('VENDOR_NOT_FOUND', 'Vendor not found');
  }

  if (
    vendor.accountState !== 'PENDING_FIRST_APPROVAL' &&
    vendor.accountState !== 'PENDING_PROCESSOR'
  ) {
    throw new VendorManagementError(
      'INVALID_STATE',
      `Cannot activate vendor with state ${vendor.accountState}. Vendor must be PENDING_FIRST_APPROVAL or PENDING_PROCESSOR.`,
    );
  }

  const isForceActivate = vendor.accountState === 'PENDING_PROCESSOR';

  await setVendorAccountState(db, vendorId, 'ACTIVE');
  await bumpMhVersion(db, vendor.ownerUserId);

  await recordAdminAction(db, {
    adminId,
    targetType: 'VENDOR',
    targetId: vendorId,
    action: 'APPROVE',
    note:
      note ??
      (isForceActivate ? 'Force activated - payment setup skipped' : 'First approval - activated'),
  });

  await invalidateCatalog(db, { scope: 'global' });
}

// ─── freezeVendor ─────────────────────────────────────────────────────────────

export async function freezeVendor(
  deps: VendorManagementDeps,
  input: FreezeVendorInput,
): Promise<void> {
  const { db } = deps;
  const { adminId, vendorId, reason, durationDays } = input;

  // Dynamic import to avoid compile-order cycle with Agent 2C's violation workflow
  const violationModule = await import('../../../workflows/violation.js').catch((err) => {
    captureCaught(err, { scope: 'server.admin.vendor-management', severity: 'info' });
    return null;
  });

  if (violationModule?.freezeVendor) {
    await violationModule.freezeVendor(
      {
        db: deps.db,
        email: deps.email,
        getVendorEmail: async () => null, // email is PII-encrypted; caller handles decryption
      },
      { adminId, vendorId, reason, durationDays },
    );
    return;
  }

  // Fallback: direct DB update if violation workflow not yet available
  const [vendor] = await db
    .select({
      id: vendors.id,
      accountState: vendors.accountState,
      ownerUserId: vendors.ownerUserId,
    })
    .from(vendors)
    .where(eq(vendors.id, vendorId))
    .limit(1);

  if (!vendor) {
    throw new VendorManagementError('VENDOR_NOT_FOUND', 'Vendor not found');
  }
  if (vendor.accountState === 'BANNED') {
    throw new VendorManagementError('INVALID_STATE', 'Cannot freeze a banned vendor');
  }

  await setVendorAccountState(db, vendorId, 'FROZEN');
  await bumpMhVersion(db, vendor.ownerUserId);

  await recordAdminAction(db, {
    adminId,
    targetType: 'VENDOR',
    targetId: vendorId,
    action: 'FREEZE',
    note: reason,
  });

  await invalidateCatalog(db, { scope: 'global' });
}

// ─── unfreezeVendor ───────────────────────────────────────────────────────────

export async function unfreezeVendor(
  deps: VendorManagementDeps,
  input: UnfreezeVendorInput,
): Promise<void> {
  const { db } = deps;
  const { adminId, vendorId, note } = input;

  const [vendor] = await db
    .select({
      id: vendors.id,
      accountState: vendors.accountState,
      tier: vendors.tier,
      ownerUserId: vendors.ownerUserId,
    })
    .from(vendors)
    .where(eq(vendors.id, vendorId))
    .limit(1);

  if (!vendor) {
    throw new VendorManagementError('VENDOR_NOT_FOUND', 'Vendor not found');
  }
  if (vendor.accountState !== 'FROZEN') {
    throw new VendorManagementError('INVALID_STATE', 'Vendor is not frozen');
  }

  // Restore to ACTIVE or VETERAN based on tier
  const restoredState = vendor.tier === 'VETERAN' ? 'VETERAN' : 'ACTIVE';

  await setVendorAccountState(db, vendorId, restoredState);
  await bumpMhVersion(db, vendor.ownerUserId);

  await recordAdminAction(db, {
    adminId,
    targetType: 'VENDOR',
    targetId: vendorId,
    action: 'UNFREEZE',
    note: note ?? null,
  });

  await invalidateCatalog(db, { scope: 'global' });
}

// ─── banVendor ────────────────────────────────────────────────────────────────

export async function banVendor(deps: VendorManagementDeps, input: BanVendorInput): Promise<void> {
  const { db } = deps;
  const { adminId, vendorId, reason } = input;

  const [vendor] = await db
    .select({
      id: vendors.id,
      accountState: vendors.accountState,
      email: vendors.email,
      displayName: vendors.displayName,
      ownerUserId: vendors.ownerUserId,
    })
    .from(vendors)
    .where(eq(vendors.id, vendorId))
    .limit(1);

  if (!vendor) {
    throw new VendorManagementError('VENDOR_NOT_FOUND', 'Vendor not found');
  }
  if (vendor.accountState === 'BANNED') {
    throw new VendorManagementError('INVALID_STATE', 'Vendor is already banned');
  }

  await setVendorAccountState(db, vendorId, 'BANNED');
  await bumpMhVersion(db, vendor.ownerUserId);

  await recordAdminAction(db, {
    adminId,
    targetType: 'VENDOR',
    targetId: vendorId,
    action: 'BAN',
    note: reason,
  });

  await invalidateCatalog(db, { scope: 'global' });

  // Notify vendor via email (best-effort)
  try {
    if (vendor.email) {
      await sendPolicyViolation(deps.email, {
        to: vendor.email,
        vendorName: vendor.displayName,
        action: 'BAN',
        reason,
        nextSteps: 'Contact support if you believe this is an error.',
      });
    }
  } catch (err) {
    captureCaught(err, { scope: 'server.admin.vendor-management', severity: 'warning' });
    // Non-fatal
  }
}

// ─── sendMessageToVendor ──────────────────────────────────────────────────────

export async function sendMessageToVendor(
  deps: VendorManagementDeps,
  input: SendVendorMessageInput,
): Promise<void> {
  const { db } = deps;
  const { adminId, vendorId, subject, body } = input;

  const [vendor] = await db
    .select({
      id: vendors.id,
      email: vendors.email,
      displayName: vendors.displayName,
      ownerUserId: vendors.ownerUserId,
    })
    .from(vendors)
    .where(eq(vendors.id, vendorId))
    .limit(1);

  if (!vendor) {
    throw new VendorManagementError('VENDOR_NOT_FOUND', 'Vendor not found');
  }

  // Create in-app message via purchase_messages table as an admin→vendor message
  // Uses a sentinel purchaseId approach: for admin messages we use an outbox event
  // to avoid coupling to a specific purchase. Insert an outbox notification instead.
  const { enqueueOutbox } = await import('@/server/queues/outbox-producer.js');
  const { insertOutboxRow } = await import('@/server/db/queries/outbox.js');
  const { id: vendorMsgOutboxId } = await insertOutboxRow(db, {
    aggregateType: 'admin',
    aggregateId: vendorId as unknown as string,
    eventType: 'admin.vendor_message',
    payload: {
      adminId,
      vendorId,
      subject,
      body,
    },
  });
  await enqueueOutbox(vendorMsgOutboxId);

  // Send push notification to vendor owner (best-effort)
  try {
    await deps.push.sendToVendor(vendorId, {
      title: subject,
      body: body.slice(0, 120),
      tag: 'admin-message',
    });
  } catch (err) {
    captureCaught(err, { scope: 'server.admin.vendor-management', severity: 'warning' });
    // Non-fatal
  }

  // Send email (best-effort)
  try {
    if (vendor.email) {
      await sendPolicyViolation(deps.email, {
        to: vendor.email,
        vendorName: vendor.displayName,
        action: 'WARNING',
        reason: subject,
        nextSteps: body,
      });
    }
  } catch (err) {
    captureCaught(err, { scope: 'server.admin.vendor-management', severity: 'warning' });
    // Non-fatal
  }
}

// ─── approveAnywayVendor ──────────────────────────────────────────────────────

/**
 * Admin override: approve a PENDING (flagged) or REJECTED vendor anyway.
 * Transitions vendor to PENDING_PROCESSOR, clears flagReason/rejectReason,
 * sets llmDecision to 'APPROVE', then triggers Stripe Connect onboarding (Phase 3A).
 */
export async function approveAnywayVendor(
  deps: VendorManagementDeps,
  input: ApproveAnywayInput,
): Promise<void> {
  const { db } = deps;
  const { adminId, vendorId, note } = input;

  const vendor = await findVendorById(db, vendorId);

  if (!vendor) {
    throw new VendorManagementError('VENDOR_NOT_FOUND', 'Vendor not found');
  }

  if (vendor.accountState !== 'PENDING_FIRST_APPROVAL' && vendor.accountState !== 'REJECTED') {
    throw new VendorManagementError(
      'INVALID_STATE',
      `Cannot approve vendor with state ${vendor.accountState}. Vendor must be PENDING_FIRST_APPROVAL or REJECTED.`,
    );
  }

  await approveVendorAnyway(db, vendorId);

  await recordAdminAction(db, {
    adminId,
    targetType: 'VENDOR',
    targetId: vendorId,
    action: 'APPROVE',
    note: note ?? 'Approved anyway - overriding AI decision',
  });

  // Stripe Connect onboarding triggered by admin approval (Phase 3A)
}
