import { NextRequest, NextResponse } from 'next/server';
import { and, eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { withTransaction } from '@/lib/db/utils';
import { ports } from '@/lib/db/schema/ports';
import { residentialClients, residentialInterests } from '@/lib/db/schema/residential';
import { systemSettings } from '@/lib/db/schema/system';
import { sendEmail } from '@/lib/email';
import {
  residentialClientConfirmation,
  residentialSalesAlert,
} from '@/lib/email/templates/residential-inquiry';
import { resolveSubject } from '@/lib/email/resolve-subject';
import { getBrandingShell } from '@/lib/email/branding-resolver';
import { getPortBrandingConfig, getPortEmailConfig } from '@/lib/services/port-config';
import { assertResidentialModuleEnabled } from '@/lib/services/residential-module.service';
import { env } from '@/lib/env';
import { parseBody } from '@/lib/api/route-helpers';
import { errorResponse, RateLimitError, ValidationError } from '@/lib/errors';
import { logger } from '@/lib/logger';
import { checkRateLimit, rateLimiters } from '@/lib/rate-limit';
import { publicResidentialInquirySchema } from '@/lib/validators/residential';
import { emitToRoom } from '@/lib/socket/server';
import { parsePhone } from '@/lib/i18n/phone';
import type { CountryCode } from '@/lib/i18n/countries';

/**
 * Throws RateLimitError if the IP has exceeded the public-form quota.
 * Backed by the Redis sliding-window limiter so the cap survives restarts
 * and is shared across worker processes.
 */
async function gateRateLimit(ip: string): Promise<void> {
  const result = await checkRateLimit(ip, rateLimiters.publicForm);
  if (!result.allowed) {
    const retryAfter = Math.max(1, Math.ceil((result.resetAt - Date.now()) / 1000));
    throw new RateLimitError(retryAfter);
  }
}

/**
 * POST /api/public/residential-inquiries - unauthenticated entry point for
 * the public website's residential interest form. Creates a
 * `residential_clients` row and an opening `residential_interests` row in a
 * single transaction.
 *
 * Required: `portId` query param or `X-Port-Id` header.
 */
export async function POST(req: NextRequest) {
  try {
    const ip = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown';
    await gateRateLimit(ip);

    const data = await parseBody(req, publicResidentialInquirySchema);

    const portId = req.nextUrl.searchParams.get('portId') ?? req.headers.get('X-Port-Id');
    if (!portId) {
      throw new ValidationError('portId is required');
    }
    const port = await db.query.ports.findFirst({ where: eq(ports.id, portId) });
    if (!port) {
      throw new ValidationError('Unknown port');
    }

    // Reject when the port has the Residential module turned off — a
    // disabled port shouldn't silently accept residential leads it can't
    // see in the CRM. Throws NotFoundError → 404 (mirrors the v1 route
    // + entity-tab gates for the other module toggles).
    await assertResidentialModuleEnabled(portId);

    // If the website didn't pre-normalize, parse server-side. International
    // strings parse without a hint; national-format submissions need a country.
    let phoneE164 = data.phoneE164 ?? null;
    let phoneCountry: CountryCode | null = (data.phoneCountry as CountryCode | null) ?? null;
    if (!phoneE164) {
      const parsed = parsePhone(data.phone, phoneCountry ?? undefined);
      phoneE164 = parsed.e164;
      phoneCountry = parsed.country ?? phoneCountry;
    }

    const result = await withTransaction(async (tx) => {
      const [client] = await tx
        .insert(residentialClients)
        .values({
          portId,
          fullName: `${data.firstName.trim()} ${data.lastName.trim()}`.trim(),
          email: data.email,
          phone: data.phone,
          phoneE164,
          phoneCountry,
          nationalityIso: data.nationalityIso ?? null,
          timezone: data.timezone ?? null,
          placeOfResidence: data.placeOfResidence,
          placeOfResidenceCountryIso: data.placeOfResidenceCountryIso ?? null,
          subdivisionIso: data.subdivisionIso ?? null,
          preferredContactMethod: data.preferredContactMethod,
          source: 'website',
          status: 'prospect',
          notes: data.notes,
        })
        .returning();
      if (!client) throw new Error('Failed to create residential client');

      const [interest] = await tx
        .insert(residentialInterests)
        .values({
          portId,
          residentialClientId: client.id,
          pipelineStage: 'new',
          source: 'website',
          notes: data.notes,
          preferences: data.preferences,
        })
        .returning();
      if (!interest) throw new Error('Failed to create residential interest');

      return { clientId: client.id, interestId: interest.id };
    });

    emitToRoom(`port:${portId}`, 'residential_client:created', { id: result.clientId });
    emitToRoom(`port:${portId}`, 'residential_interest:created', { id: result.interestId });

    // Send notification emails (non-blocking - failures shouldn't 500 the
    // public form).
    void sendResidentialNotifications({
      portId,
      data,
      crmDeepLink: `${env.APP_URL}/${port.slug}/residential/clients/${result.clientId}`,
    }).catch((err) => logger.error({ err }, 'Failed to send residential inquiry notifications'));

    // L34 carve-out: deliberate bespoke `{ success: true, ... }` shape
    // (NOT the `{ data }` envelope). This is the public website's intake
    // contract — the external marketing site reads `success` and the
    // returned ids off the JSON root, mirroring the public portal-auth
    // endpoints. Changing the shape would be a breaking cross-repo change.
    return NextResponse.json({ success: true, ...result }, { status: 201 });
  } catch (error) {
    return errorResponse(error);
  }
}

async function sendResidentialNotifications(args: {
  portId: string;
  data: {
    firstName: string;
    lastName: string;
    email: string;
    phone: string;
    placeOfResidence?: string;
    preferredContactMethod?: 'email' | 'phone';
    notes?: string;
    preferences?: string;
  };
  crmDeepLink: string;
}): Promise<void> {
  const { portId, data, crmDeepLink } = args;

  const [branding, portBrand, emailCfg] = await Promise.all([
    getBrandingShell(portId),
    getPortBrandingConfig(portId).catch(() => null),
    getPortEmailConfig(portId).catch(() => null),
  ]);
  // Use the port's configured From address (or branding app name) for
  // the "contact us" line on the confirmation email so other ports don't
  // direct replies to sales@portnimara.com.
  const contactEmail = emailCfg?.fromAddress ?? '';
  const portName = portBrand?.appName ?? 'our team';

  // Client confirmation
  const confirmation = await residentialClientConfirmation(
    {
      firstName: data.firstName,
      contactEmail,
    },
    { branding },
  );
  const confirmationSubject = await resolveSubject({
    key: 'residential_inquiry_client_confirmation',
    portId,
    fallback: confirmation.subject,
    tokens: { portName, recipientName: data.firstName },
  });
  await sendEmail(data.email, confirmationSubject, confirmation.html, undefined, undefined, portId);

  // Sales-team alert - pull recipients from system_settings if configured;
  // fall back to the inquiry_contact_email if available.
  const recipientsRow = await db.query.systemSettings.findFirst({
    where: and(
      eq(systemSettings.key, 'residential_notification_recipients'),
      eq(systemSettings.portId, portId),
    ),
  });
  const fallbackRow = await db.query.systemSettings.findFirst({
    where: and(eq(systemSettings.key, 'inquiry_contact_email'), eq(systemSettings.portId, portId)),
  });

  const configured = Array.isArray(recipientsRow?.value) ? (recipientsRow!.value as string[]) : [];
  const fallback =
    typeof fallbackRow?.value === 'string' && fallbackRow.value.length > 0
      ? [fallbackRow.value]
      : [];
  const recipients = configured.length > 0 ? configured : fallback;

  if (recipients.length === 0) {
    logger.warn(
      { portId },
      'No residential_notification_recipients or inquiry_contact_email configured; skipping sales alert',
    );
    return;
  }

  const alert = await residentialSalesAlert(
    {
      fullName: `${data.firstName} ${data.lastName}`.trim(),
      email: data.email,
      phone: data.phone,
      placeOfResidence: data.placeOfResidence,
      preferredContactMethod: data.preferredContactMethod,
      notes: data.notes,
      preferences: data.preferences,
      crmDeepLink,
    },
    { branding },
  );
  const alertSubject = await resolveSubject({
    key: 'residential_inquiry_sales_alert',
    portId,
    fallback: alert.subject,
    tokens: {
      portName,
      clientName: `${data.firstName} ${data.lastName}`.trim(),
      email: data.email,
      phone: data.phone,
    },
  });

  await sendEmail(recipients, alertSubject, alert.html);
}
