"use server";

import { revalidatePath } from "next/cache";
import { Op } from "sequelize";
import { initDb } from "@/lib/db";
import { requireClientWorkspace } from "@/lib/auth/require-auth";
import { sendEmail } from "@/lib/email/mailer";
import { getScopedSurvey } from "@/lib/queries/survey-queries";
import { slugify, randomSlugSuffix } from "@/lib/utils/slugify";
import {
  createContactSchema,
  updateContactSchema,
  importContactsSchema,
  createContactListSchema,
  addContactsToListSchema,
  sendListInviteSchema,
} from "@/lib/validation/audience";
import type { ActionResult } from "@/lib/actions/auth";

const AUDIENCE_PATH = "/app/audience";

/** Create a single contact. */
export async function createContact(input: {
  name: string;
  email: string;
  phone?: string | null;
  tags?: string[];
  notes?: string | null;
}): Promise<ActionResult<{ contactId: string }>> {
  const { workspaceId, userId } = await requireClientWorkspace();

  const parsed = createContactSchema.safeParse(input);
  if (!parsed.success) {
    return { success: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
  }

  const { Contact, AuditLog } = initDb();
  const email = parsed.data.email.toLowerCase();

  const existing = await Contact.findOne({ where: { workspaceId, email } });
  if (existing) {
    return { success: false, error: "A contact with this email already exists" };
  }

  const contact = await Contact.create({
    workspaceId,
    name: parsed.data.name,
    email,
    phone: parsed.data.phone ?? null,
    tags: parsed.data.tags ?? [],
    notes: parsed.data.notes ?? null,
    lastInvitedAt: null,
  });

  await AuditLog.create({
    actorUserId: userId,
    workspaceId,
    action: "contact.created",
    metadata: { contactId: contact.id },
  });

  revalidatePath(AUDIENCE_PATH);
  return { success: true, data: { contactId: contact.id } };
}

/** Update an existing contact's details. */
export async function updateContact(input: {
  contactId: string;
  name?: string;
  email?: string;
  phone?: string | null;
  tags?: string[];
  notes?: string | null;
}): Promise<ActionResult> {
  const { workspaceId } = await requireClientWorkspace();

  const parsed = updateContactSchema.safeParse(input);
  if (!parsed.success) {
    return { success: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
  }

  const { Contact } = initDb();
  const contact = await Contact.findOne({
    where: { id: parsed.data.contactId, workspaceId },
  });
  if (!contact) return { success: false, error: "Contact not found" };

  if (parsed.data.email) {
    const email = parsed.data.email.toLowerCase();
    const dupe = await Contact.findOne({
      where: { workspaceId, email, id: { [Op.ne]: contact.id } },
    });
    if (dupe) return { success: false, error: "Another contact already uses this email" };
  }

  await contact.update({
    ...(parsed.data.name !== undefined && { name: parsed.data.name }),
    ...(parsed.data.email !== undefined && { email: parsed.data.email.toLowerCase() }),
    ...(parsed.data.phone !== undefined && { phone: parsed.data.phone }),
    ...(parsed.data.tags !== undefined && { tags: parsed.data.tags }),
    ...(parsed.data.notes !== undefined && { notes: parsed.data.notes }),
  });

  revalidatePath(AUDIENCE_PATH);
  return { success: true };
}

/** Delete a single contact. */
export async function deleteContact(contactId: string): Promise<ActionResult> {
  const { workspaceId } = await requireClientWorkspace();
  const { Contact } = initDb();

  const contact = await Contact.findOne({ where: { id: contactId, workspaceId } });
  if (!contact) return { success: false, error: "Contact not found" };

  await contact.destroy();
  revalidatePath(AUDIENCE_PATH);
  return { success: true };
}

/** Delete multiple contacts at once. */
export async function bulkDeleteContacts(
  contactIds: string[]
): Promise<ActionResult<{ deleted: number }>> {
  const { workspaceId } = await requireClientWorkspace();
  if (!contactIds.length) return { success: false, error: "No contacts selected" };

  const { Contact } = initDb();
  const deleted = await Contact.destroy({
    where: { id: { [Op.in]: contactIds }, workspaceId },
  });

  revalidatePath(AUDIENCE_PATH);
  return { success: true, data: { deleted } };
}

function parseContactLines(raw: string): { name: string; email: string; phone?: string }[] {
  const lines = raw
    .split(/\r?\n/)
    .map((l) => l.trim())
    .filter(Boolean);

  const results: { name: string; email: string; phone?: string }[] = [];

  for (const line of lines) {
    const parts = line.split(/[,;\t]+/).map((p) => p.trim()).filter(Boolean);
    const emailIdx = parts.findIndex((p) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(p));
    if (emailIdx === -1) continue;

    const email = parts[emailIdx].toLowerCase();
    const name = parts.find((p, i) => i !== emailIdx && !/^\+?[\d\s()-]{7,}$/.test(p)) ??
      email.split("@")[0];
    const phone = parts.find((p, i) => i !== emailIdx && /^\+?[\d\s()-]{7,}$/.test(p));

    results.push({ name, email, phone });
  }

  return results;
}

/** Bulk import contacts pasted as free text (name, email, phone per line). */
export async function importContacts(input: {
  raw: string;
  listId?: string | null;
}): Promise<ActionResult<{ imported: number; skipped: number }>> {
  const { workspaceId, userId } = await requireClientWorkspace();

  const parsed = importContactsSchema.safeParse(input);
  if (!parsed.success) {
    return { success: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
  }

  const rows = parseContactLines(parsed.data.raw);
  if (rows.length === 0) {
    return { success: false, error: "No valid email addresses found in the pasted text" };
  }

  const { Contact, ContactList, ContactListMember, AuditLog, sequelize } = initDb();

  let imported = 0;
  let skipped = 0;
  const createdIds: string[] = [];

  await sequelize.transaction(async (t) => {
    for (const row of rows) {
      const existing = await Contact.findOne({
        where: { workspaceId, email: row.email },
        transaction: t,
      });
      if (existing) {
        skipped++;
        createdIds.push(existing.id);
        continue;
      }

      const contact = await Contact.create(
        {
          workspaceId,
          name: row.name,
          email: row.email,
          phone: row.phone ?? null,
          tags: [],
          notes: null,
          lastInvitedAt: null,
        },
        { transaction: t }
      );
      imported++;
      createdIds.push(contact.id);
    }

    if (parsed.data.listId) {
      const list = await ContactList.findOne({
        where: { id: parsed.data.listId, workspaceId },
        transaction: t,
      });
      if (list) {
        for (const contactId of createdIds) {
          await ContactListMember.findOrCreate({
            where: { contactListId: list.id, contactId },
            defaults: { contactListId: list.id, contactId },
            transaction: t,
          });
        }
      }
    }

    await AuditLog.create(
      {
        actorUserId: userId,
        workspaceId,
        action: "contact.imported",
        metadata: { imported, skipped },
      },
      { transaction: t }
    );
  });

  revalidatePath(AUDIENCE_PATH);
  return { success: true, data: { imported, skipped } };
}

/** Create a new contact list (segment). */
export async function createContactList(input: {
  name: string;
  description?: string | null;
}): Promise<ActionResult<{ listId: string }>> {
  const { workspaceId, userId } = await requireClientWorkspace();

  const parsed = createContactListSchema.safeParse(input);
  if (!parsed.success) {
    return { success: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
  }

  const { ContactList, AuditLog } = initDb();
  const list = await ContactList.create({
    workspaceId,
    name: parsed.data.name,
    description: parsed.data.description ?? null,
  });

  await AuditLog.create({
    actorUserId: userId,
    workspaceId,
    action: "contact_list.created",
    metadata: { listId: list.id },
  });

  revalidatePath(AUDIENCE_PATH);
  return { success: true, data: { listId: list.id } };
}

/** Delete a contact list (contacts themselves are not deleted). */
export async function deleteContactList(listId: string): Promise<ActionResult> {
  const { workspaceId } = await requireClientWorkspace();
  const { ContactList } = initDb();

  const list = await ContactList.findOne({ where: { id: listId, workspaceId } });
  if (!list) return { success: false, error: "List not found" };

  await list.destroy();
  revalidatePath(AUDIENCE_PATH);
  return { success: true };
}

/** Add one or more contacts to a list. */
export async function addContactsToList(input: {
  listId: string;
  contactIds: string[];
}): Promise<ActionResult<{ added: number }>> {
  const { workspaceId } = await requireClientWorkspace();

  const parsed = addContactsToListSchema.safeParse(input);
  if (!parsed.success) {
    return { success: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
  }

  const { ContactList, Contact, ContactListMember } = initDb();
  const list = await ContactList.findOne({
    where: { id: parsed.data.listId, workspaceId },
  });
  if (!list) return { success: false, error: "List not found" };

  const validContacts = await Contact.findAll({
    where: { id: { [Op.in]: parsed.data.contactIds }, workspaceId },
    attributes: ["id"],
  });

  let added = 0;
  for (const contact of validContacts) {
    const [, created] = await ContactListMember.findOrCreate({
      where: { contactListId: list.id, contactId: contact.id },
      defaults: { contactListId: list.id, contactId: contact.id },
    });
    if (created) added++;
  }

  revalidatePath(AUDIENCE_PATH);
  return { success: true, data: { added } };
}

/** Remove a contact from a list. */
export async function removeContactFromList(input: {
  listId: string;
  contactId: string;
}): Promise<ActionResult> {
  const { workspaceId } = await requireClientWorkspace();
  const { ContactList, ContactListMember } = initDb();

  const list = await ContactList.findOne({
    where: { id: input.listId, workspaceId },
  });
  if (!list) return { success: false, error: "List not found" };

  await ContactListMember.destroy({
    where: { contactListId: input.listId, contactId: input.contactId },
  });

  revalidatePath(AUDIENCE_PATH);
  return { success: true };
}

function escapeHtml(text: string): string {
  return text
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}

/** Send a survey invite to every contact in a list via email. */
export async function sendSurveyToList(input: {
  surveyId: string;
  listId: string;
  subject: string;
  body: string;
}): Promise<ActionResult<{ sent: number; failed: number }>> {
  const { userId, workspaceId } = await requireClientWorkspace();

  const parsed = sendListInviteSchema.safeParse(input);
  if (!parsed.success) {
    return { success: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
  }

  const survey = await getScopedSurvey(parsed.data.surveyId, workspaceId);
  if (!survey) return { success: false, error: "Survey not found" };
  if (survey.status !== "active") {
    return { success: false, error: "Publish the survey before sending invites" };
  }

  const { ContactList, Contact, Collector, AuditLog } = initDb();
  const list = await ContactList.findOne({
    where: { id: parsed.data.listId, workspaceId },
    include: [{ model: Contact, as: "contacts" }],
  });
  if (!list) return { success: false, error: "List not found" };

  const contacts = (list.get("contacts") as InstanceType<typeof Contact>[] | undefined) ?? [];
  if (contacts.length === 0) {
    return { success: false, error: "This list has no contacts" };
  }

  let collector = await Collector.findOne({
    where: { surveyId: parsed.data.surveyId, type: "web_link" },
  });
  if (!collector) {
    const baseSlug = slugify(survey.title) || "survey";
    collector = await Collector.create({
      surveyId: parsed.data.surveyId,
      type: "web_link",
      slug: `${baseSlug}-${randomSlugSuffix()}`,
      isActive: true,
      config: {},
    });
  }

  const baseUrl =
    process.env.NEXT_PUBLIC_APP_URL ??
    process.env.NEXTAUTH_URL ??
    "http://localhost:3000";
  const surveyUrl = `${baseUrl}/s/${collector.slug}`;

  const htmlBody = `
    <p>${escapeHtml(parsed.data.body).replace(/\n/g, "<br>")}</p>
    <p><a href="${surveyUrl}">${surveyUrl}</a></p>
  `;

  let sent = 0;
  let failed = 0;

  for (const contact of contacts) {
    const result = await sendEmail({
      to: contact.email,
      subject: parsed.data.subject,
      html: htmlBody,
    });
    if (result.ok) {
      sent++;
      await contact.update({
        lastInvitedAt: new Date(),
        inviteCount: contact.inviteCount + 1,
      });
    } else {
      failed++;
    }
  }

  await AuditLog.create({
    actorUserId: userId,
    workspaceId,
    action: "contact_list.invite_sent",
    metadata: { surveyId: parsed.data.surveyId, listId: parsed.data.listId, sent, failed },
  });

  revalidatePath(AUDIENCE_PATH);
  return { success: true, data: { sent, failed } };
}
