"use server";

import { and, eq, ilike, or, sql, SQL } from "drizzle-orm";
import { getAdminUser } from "@/lib/admin-permissions";
import { parseCSVWithLimits } from "@/lib/csv-parser";
import { buildImportPreview } from "@/lib/column-detector";
import { withBrand, withPlatformAdmin } from "@/db/client";
import { customers } from "@/db/schema";

export type ContactSource = "order" | "import" | "manual" | "admin";

/**
 * The new `customers` table stores only the fields needed to send
 * communications. The legacy `communication_contacts` table also tracked
 * source/external_id/customer_id/tags/metadata, which are no longer
 * modeled. Fields below that mirror the new columns are kept; the rest
 * are dropped. UI code that previously rendered e.g. `tags` is expected
 * to degrade gracefully when those fields are absent.
 */
export type Contact = {
  id: string;
  brand_id: string;
  email: string | null;
  phone: string | null;
  full_name: string;
  /** Legacy field — derived from full_name, may be null when only a single token is stored */
  first_name: string | null;
  /** Legacy field — derived from full_name, may be null when only a single token is stored */
  last_name: string | null;
  source: ContactSource;
  email_opt_in: boolean;
  sms_opt_in: boolean;
  /** Legacy field — null when neither email nor sms is opted out */
  unsubscribed_at: string | null;
  created_at: string;
  updated_at: string;
};

export type ContactImportEntry = {
  email?: string;
  phone?: string;
  first_name?: string;
  last_name?: string;
  full_name?: string;
  tags?: string[];
  email_opt_in?: boolean;
  sms_opt_in?: boolean;
  external_id?: string;
  /** Ignored columns from the CSV, stored in metadata on import */
  _metadata?: Record<string, string>;
};

export type ImportResult = {
  created: number;
  updated: number;
  skipped: number;
  errors: { row: ContactImportEntry; error: string }[];
};

// ── Import preview types (column detection) ────────────────────────────────

export type ImportField =
  | "email"
  | "phone"
  | "first_name"
  | "last_name"
  | "full_name"
  | "tags"
  | "email_opt_in"
  | "sms_opt_in"
  | "external_id"
  | null;

export type ColumnMapping = {
  csvColumn: string;
  field: ImportField;
  sampleValues: string[];
};

export type ImportPreviewResult = {
  mappings: ColumnMapping[];
  totalRows: number;
  validRows: number;
  skippedRows: number;
  duplicateRows: number;
  skippedReasons: { rowIndex: number; reason: string }[];
  sampleRows: ContactImportEntry[];
  ignoredColumns: string[];
  warnings: string[];
};

export type GetContactsResult = {
  success: true;
  contacts: Contact[];
  total: number;
  limit: number;
  offset: number;
} | {
  success: false;
  error: string;
};

function rowToContact(row: typeof customers.$inferSelect): Contact {
  const firstName = row.firstName;
  const lastName = row.lastName;
  const fullName = [firstName, lastName].filter(Boolean).join(" ") || "";

  // Derive unsubscribed_at: the new schema only has opt-in flags, not
  // a timestamp. Mark the unsubscribe moment as updatedAt if opted out
  // of both channels; null while either is still opted in.
  const fullyUnsubscribed = !row.emailOptIn && !row.smsOptIn;

  return {
    id: row.id,
    brand_id: row.brandId,
    email: row.email,
    phone: row.phone,
    full_name: row.fullName ?? "",
    first_name: firstName,
    last_name: lastName,
    source: "manual",
    email_opt_in: row.emailOptIn,
    sms_opt_in: row.smsOptIn,
    unsubscribed_at: fullyUnsubscribed ? row.updatedAt.toISOString() : null,
    created_at: row.createdAt.toISOString(),
    updated_at: row.updatedAt.toISOString(),
  };
}

export async function getContacts(params: {
  brandId: string;
  search?: string;
  source?: string;
  limit?: number;
  offset?: number;
}): Promise<GetContactsResult> {
  const adminUser = await getAdminUser();
  if (!adminUser) return { success: false, error: "Not authenticated" };
  if (
    adminUser.role === "brand_admin" &&
    adminUser.brand_id !== params.brandId
  ) {
    return { success: false, error: "Not authorized for this brand" };
  }

  const limit = params.limit ?? 100;
  const offset = params.offset ?? 0;
  const search = params.search?.trim() ?? "";

  try {
    const conds: SQL[] = [eq(customers.brandId, params.brandId)];
    if (search) {
      const like = `%${search}%`;
      conds.push(or(ilike(customers.firstName, like), ilike(customers.email, like))!);
    }

    const rows = await withBrand(params.brandId, async (db) => {
      const [items, countRows] = await Promise.all([
        db
          .select()
          .from(customers)
          .where(and(...conds))
          .limit(limit)
          .offset(offset)
          .orderBy(customers.createdAt),
        db
          .select({ value: sql<number>`count(*)::int` })
          .from(customers)
          .where(and(...conds)),
      ]);
      return { items, total: Number(countRows[0]?.value ?? 0) };
    });

    return {
      success: true,
      contacts: rows.items.map(rowToContact),
      total: rows.total,
      limit,
      offset,
    };
  } catch (err) {
    return {
      success: false,
      error: err instanceof Error ? err.message : "Failed to fetch contacts",
    };
  }
}

export type UpsertContactResult = {
  success: true;
  contact: Contact;
} | {
  success: false;
  error: string;
};

export async function upsertContact(contact: {
  id?: string;
  brand_id: string;
  email?: string;
  phone?: string;
  first_name?: string;
  last_name?: string;
  full_name?: string;
  source: ContactSource;
  external_id?: string;
  customer_id?: string;
  email_opt_in?: boolean;
  sms_opt_in?: boolean;
  tags?: string[];
  metadata?: Record<string, unknown>;
}): Promise<UpsertContactResult> {
  const adminUser = await getAdminUser();
  if (!adminUser) return { success: false, error: "Not authenticated" };
  if (
    adminUser.role === "brand_admin" &&
    adminUser.brand_id !== contact.brand_id
  ) {
    return { success: false, error: "Not authorized for this brand" };
  }

  const fullName =
    contact.full_name?.trim() ||
    [contact.first_name, contact.last_name].filter(Boolean).join(" ").trim() ||
    contact.email ||
    contact.phone ||
    "Unknown";

  try {
    if (contact.id) {
      const contactId = contact.id;
      const updated = await withBrand(contact.brand_id, (db) =>
        db
          .update(customers)
          .set({
            fullName,
            email: contact.email ?? null,
            phone: contact.phone ?? null,
            firstName: contact.first_name ?? null,
            lastName: contact.last_name ?? null,
            smsOptIn: contact.sms_opt_in ?? false,
            emailOptIn: contact.email_opt_in ?? true,
            updatedAt: new Date(),
          })
          .where(and(eq(customers.id, contactId), eq(customers.brandId, contact.brand_id)))
          .returning(),
      );
      const row = updated[0];
      if (!row) return { success: false, error: "Contact not found" };
      return { success: true, contact: rowToContact(row) };
    }

    // INSERT — de-dupe on (brand_id, email) when email is provided
    const contactEmail = contact.email;
    if (contactEmail) {
      const existing = await withBrand(contact.brand_id, (db) =>
        db
          .select()
          .from(customers)
          .where(and(eq(customers.email, contactEmail), eq(customers.brandId, contact.brand_id)))
          .limit(1),
      );
      if (existing[0]) {
        const updated = await withBrand(contact.brand_id, (db) =>
          db
            .update(customers)
            .set({
              fullName,
              phone: contact.phone ?? null,
              firstName: contact.first_name ?? null,
              lastName: contact.last_name ?? null,
              smsOptIn: contact.sms_opt_in ?? existing[0].smsOptIn,
              emailOptIn: contact.email_opt_in ?? existing[0].emailOptIn,
              updatedAt: new Date(),
            })
            .where(eq(customers.id, existing[0].id))
            .returning(),
        );
        const row = updated[0];
        if (!row) return { success: false, error: "Failed to upsert contact" };
        return { success: true, contact: rowToContact(row) };
      }
    }

    const inserted = await withBrand(contact.brand_id, (db) =>
      db
        .insert(customers)
        .values({
          brandId: contact.brand_id,
          fullName,
          email: contact.email ?? null,
          phone: contact.phone ?? null,
          firstName: contact.first_name ?? null,
          lastName: contact.last_name ?? null,
          smsOptIn: contact.sms_opt_in ?? false,
          emailOptIn: contact.email_opt_in ?? true,
        })
        .returning(),
    );
    const row = inserted[0];
    if (!row) return { success: false, error: "Failed to insert contact" };
    return { success: true, contact: rowToContact(row) };
  } catch (err) {
    return {
      success: false,
      error: err instanceof Error ? err.message : "Failed to upsert contact",
    };
  }
}

export type ImportContactsResult = {
  success: true;
  result: ImportResult;
} | {
  success: false;
  error: string;
};

/**
 * The legacy `import_communication_contacts_batch` RPC is replaced with
 * an in-process batch: parse → dedupe → upsert per row, returning the
 * same ImportResult shape. This avoids a round-trip to the DB for each
 * row and keeps the call inside the `withBrand` transaction.
 */
export async function importContactsBatch(params: {
  brandId: string;
  contacts: ContactImportEntry[];
  allowOptInOverride?: boolean;
}): Promise<ImportContactsResult> {
  const adminUser = await getAdminUser();
  if (!adminUser) return { success: false, error: "Not authenticated" };
  if (
    adminUser.role === "brand_admin" &&
    adminUser.brand_id !== params.brandId
  ) {
    return { success: false, error: "Not authorized for this brand" };
  }

  const result: ImportResult = { created: 0, updated: 0, skipped: 0, errors: [] };

  for (const row of params.contacts) {
    if (!row.email && !row.phone) {
      result.skipped++;
      continue;
    }
    try {
      const r = await upsertContact({
        brand_id: params.brandId,
        email: row.email,
        phone: row.phone,
        first_name: row.first_name,
        last_name: row.last_name,
        full_name: row.full_name,
        source: "import",
        email_opt_in: row.email_opt_in,
        sms_opt_in: row.sms_opt_in,
        external_id: row.external_id,
        tags: row.tags,
        metadata: row._metadata,
      });
      if (r.success) result.created++;
      else {
        result.errors.push({ row, error: r.error });
      }
    } catch (err) {
      result.errors.push({
        row,
        error: err instanceof Error ? err.message : String(err),
      });
    }
  }

  return { success: true, result };
}

export type OptOutResult = {
  success: true;
} | {
  success: false;
  error: string;
};

export async function optOutContact(params: {
  email: string;
  brandId: string;
  method: "email" | "sms";
}): Promise<OptOutResult> {
  const adminUser = await getAdminUser();
  if (!adminUser) return { success: false, error: "Not authenticated" };
  if (
    adminUser.role === "brand_admin" &&
    adminUser.brand_id !== params.brandId
  ) {
    return { success: false, error: "Not authorized for this brand" };
  }

  try {
    await withBrand(params.brandId, (db) =>
      db
        .update(customers)
        .set({
          ...(params.method === "email" ? { emailOptIn: false } : { smsOptIn: false }),
          updatedAt: new Date(),
        })
        .where(and(eq(customers.email, params.email), eq(customers.brandId, params.brandId))),
    );
    return { success: true };
  } catch (err) {
    return {
      success: false,
      error: err instanceof Error ? err.message : "Failed to opt out contact",
    };
  }
}

export async function deleteContact(id: string, brandId?: string): Promise<{ success: boolean; error?: string }> {
  const adminUser = await getAdminUser();
  if (!adminUser) return { success: false, error: "Not authenticated" };
  if (
    adminUser.role === "brand_admin" &&
    brandId &&
    adminUser.brand_id !== brandId
  ) {
    return { success: false, error: "Not authorized for this brand" };
  }

  try {
    if (brandId) {
      await withBrand(brandId, (db) =>
        db
          .delete(customers)
          .where(and(eq(customers.id, id), eq(customers.brandId, brandId))),
      );
    } else {
      // platform_admin fallback — by id only
      await withPlatformAdmin((db) => db.delete(customers).where(eq(customers.id, id)));
    }
    return { success: true };
  } catch (err) {
    return {
      success: false,
      error: err instanceof Error ? err.message : "Failed to delete contact",
    };
  }
}

// ── Export preview ────────────────────────────────────────────────────────────

export type ExportContactsResult = {
  success: true;
  csv: string;
  filename: string;
} | {
  success: false;
  error: string;
};

function escapeCSVValue(value: unknown): string {
  if (value === null || value === undefined) return "";
  const str = String(value);
  if (str.includes('"') || str.includes(",") || str.includes("\n") || str.includes("\r")) {
    return '"' + str.replace(/"/g, '""') + '"';
  }
  return str;
}

export async function exportContacts(params: {
  brandId: string;
  brandSlug?: string;
  search?: string;
  source?: string;
}): Promise<ExportContactsResult> {
  const adminUser = await getAdminUser();
  if (!adminUser) return { success: false, error: "Not authenticated" };

  const effectiveBrandId =
    adminUser.role === "brand_admin" ? adminUser.brand_id! : params.brandId;

  if (!effectiveBrandId) return { success: false, error: "No brand context" };

  const allContacts: Contact[] = [];
  const batchSize = 1000;
  let offset = 0;

  try {
    while (true) {
      const rows = await withBrand(effectiveBrandId, (db) =>
        db
          .select()
          .from(customers)
          .where(
            params.search
              ? and(
                  eq(customers.brandId, effectiveBrandId),
                  or(
                    ilike(customers.firstName, `%${params.search}%`),
                    ilike(customers.email, `%${params.search}%`),
                  ),
                )
              : eq(customers.brandId, effectiveBrandId),
          )
          .limit(batchSize)
          .offset(offset)
          .orderBy(customers.createdAt),
      );

      const batch = rows.map(rowToContact);
      if (batch.length === 0) break;
      allContacts.push(...batch);
      if (batch.length < batchSize) break;
      offset += batchSize;
    }
  } catch (err) {
    return {
      success: false,
      error: err instanceof Error ? err.message : "Failed to fetch contacts",
    };
  }

  const headers = [
    "email",
    "phone",
    "full_name",
    "email_opt_in",
    "sms_opt_in",
    "created_at",
    "updated_at",
  ];

  const rows = allContacts.map((c) => [
    escapeCSVValue(c.email),
    escapeCSVValue(c.phone),
    escapeCSVValue(c.full_name),
    c.email_opt_in ? "TRUE" : "FALSE",
    c.sms_opt_in ? "TRUE" : "FALSE",
    escapeCSVValue(c.created_at),
    escapeCSVValue(c.updated_at),
  ]);

  const brandSlug = params.brandSlug ?? "contacts";
  const dateStr = new Date().toISOString().slice(0, 10);
  const filename = `${brandSlug}-${dateStr}.csv`;

  const csv = [headers.join(","), ...rows.map((r) => r.join(","))].join("\r\n");

  return { success: true, csv, filename };
}

// ── Import preview ────────────────────────────────────────────────────────────

export async function previewContactImport(
  csvText: string
): Promise<{ success: true; preview: ImportPreviewResult } | { success: false; error: string }> {
  try {
    const { csv, totalRows, warnings } = parseCSVWithLimits(csvText);

    if (warnings.length > 0 && totalRows === 0) {
      return { success: false, error: warnings[0] };
    }

    const preview = buildImportPreview(csv.headers, csv.rows);
    preview.warnings = warnings;

    return { success: true, preview };
  } catch (err) {
    return { success: false, error: err instanceof Error ? err.message : "Parse error" };
  }
}
