"use server";

import { revalidatePath } from "next/cache";
import { Op, type Transaction } from "sequelize";
import { initDb } from "@/lib/db";
import { requireClientWorkspace } from "@/lib/auth/require-auth";
import {
  addVotingEntityOwnerSchema,
  createVotingEntitySchema,
  importVotingEntitiesSchema,
  votingEntityTypeSchema,
} from "@/lib/validation/voting-entity";
import type { ActionResult } from "@/lib/actions/auth";
import type { VotingEntityType } from "@/types";

function firstZodError(error: { issues: { message: string }[] }): string {
  return error.issues[0]?.message ?? "Invalid input";
}

function revalidateVotingPaths(electionId?: string) {
  revalidatePath("/app/elections");
  if (electionId) {
    revalidatePath(`/app/elections/${electionId}`);
  }
}

function parseEntityImportLines(raw: string): {
  label: string;
  entityType: VotingEntityType;
  ownerEmail: string;
  weight: number;
}[] {
  const rows: {
    label: string;
    entityType: VotingEntityType;
    ownerEmail: string;
    weight: number;
  }[] = [];

  for (const line of raw.split(/\r?\n/)) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.toLowerCase().startsWith("label")) continue;
    const parts = trimmed.split(/[,;\t]/).map((part) => part.trim());
    if (parts.length < 3) continue;

    const [label, entityTypeRaw, ownerEmail, weightRaw] = parts;
    const parsedType = votingEntityTypeSchema.safeParse(entityTypeRaw);
    if (!label || !parsedType.success || !ownerEmail?.includes("@")) continue;

    const weight = weightRaw ? Number(weightRaw) : 1;
    rows.push({
      label,
      entityType: parsedType.data,
      ownerEmail: ownerEmail.toLowerCase(),
      weight: Number.isFinite(weight) && weight > 0 ? weight : 1,
    });
  }

  return rows;
}

export async function createVotingEntity(
  input: unknown
): Promise<ActionResult<{ votingEntityId: string }>> {
  const { userId, workspaceId } = await requireClientWorkspace();
  const parsed = createVotingEntitySchema.safeParse(input);
  if (!parsed.success) {
    return { success: false, error: firstZodError(parsed.error) };
  }

  const { sequelize, VotingEntity, VotingEntityOwner, Contact, AuditLog } =
    initDb();
  const data = parsed.data;

  const contacts = await Contact.findAll({
    where: { workspaceId, id: data.ownerContactIds },
  });
  if (contacts.length !== data.ownerContactIds.length) {
    return { success: false, error: "One or more owner contacts were not found" };
  }

  const primaryId =
    data.primaryOwnerContactId &&
    data.ownerContactIds.includes(data.primaryOwnerContactId)
      ? data.primaryOwnerContactId
      : data.ownerContactIds[0];

  try {
    const votingEntityId = await sequelize.transaction(async (t: Transaction) => {
      const entity = await VotingEntity.create(
        {
          workspaceId,
          label: data.label,
          entityType: data.entityType,
          metadata: data.metadata ?? null,
          isActive: data.isActive,
        },
        { transaction: t }
      );

      for (const contactId of data.ownerContactIds) {
        await VotingEntityOwner.create(
          {
            votingEntityId: entity.id,
            contactId,
            isPrimary: contactId === primaryId,
          },
          { transaction: t }
        );
      }

      await AuditLog.create(
        {
          actorUserId: userId,
          workspaceId,
          action: "voting_entity.created",
          metadata: { votingEntityId: entity.id, label: entity.label },
        },
        { transaction: t }
      );

      return entity.id;
    });

    revalidateVotingPaths();
    return { success: true, data: { votingEntityId } };
  } catch {
    return { success: false, error: "Failed to create voting entity" };
  }
}

export async function addVotingEntityOwner(
  input: unknown
): Promise<ActionResult> {
  const { userId, workspaceId } = await requireClientWorkspace();
  const parsed = addVotingEntityOwnerSchema.safeParse(input);
  if (!parsed.success) {
    return { success: false, error: firstZodError(parsed.error) };
  }

  const { sequelize, VotingEntity, VotingEntityOwner, Contact, AuditLog } =
    initDb();
  const entity = await VotingEntity.findOne({
    where: { id: parsed.data.votingEntityId, workspaceId },
  });
  if (!entity) return { success: false, error: "Voting entity not found" };

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

  try {
    await sequelize.transaction(async (t) => {
      const [, created] = await VotingEntityOwner.findOrCreate({
        where: {
          votingEntityId: entity.id,
          contactId: contact.id,
        },
        defaults: {
          votingEntityId: entity.id,
          contactId: contact.id,
          isPrimary: parsed.data.isPrimary,
        },
        transaction: t,
      });

      if (!created) {
        throw new Error("ALREADY_OWNER");
      }

      if (parsed.data.isPrimary) {
        await VotingEntityOwner.update(
          { isPrimary: false },
          {
            where: {
              votingEntityId: entity.id,
              contactId: { [Op.ne]: contact.id },
            },
            transaction: t,
          }
        );
        await VotingEntityOwner.update(
          { isPrimary: true },
          {
            where: { votingEntityId: entity.id, contactId: contact.id },
            transaction: t,
          }
        );
      }

      await AuditLog.create(
        {
          actorUserId: userId,
          workspaceId,
          action: "voting_entity.owner_added",
          metadata: { votingEntityId: entity.id, contactId: contact.id },
        },
        { transaction: t }
      );
    });
  } catch (err) {
    if (err instanceof Error && err.message === "ALREADY_OWNER") {
      return { success: false, error: "This contact is already an owner" };
    }
    return { success: false, error: "Failed to add owner" };
  }

  revalidateVotingPaths();
  return { success: true };
}

export async function importVotingEntitiesFromCsv(input: {
  raw: string;
}): Promise<ActionResult<{ imported: number; skipped: number }>> {
  const { userId, workspaceId } = await requireClientWorkspace();
  const parsed = importVotingEntitiesSchema.safeParse(input);
  if (!parsed.success) {
    return { success: false, error: firstZodError(parsed.error) };
  }

  const rows = parseEntityImportLines(parsed.data.raw);
  if (rows.length === 0) {
    return {
      success: false,
      error:
        "No valid rows found. Use: label, entityType, ownerEmail[, weight]",
    };
  }

  const { sequelize, VotingEntity, VotingEntityOwner, Contact, AuditLog } =
    initDb();

  let imported = 0;
  let skipped = 0;

  await sequelize.transaction(async (t) => {
    for (const row of rows) {
      const contact = await Contact.findOne({
        where: { workspaceId, email: row.ownerEmail },
        transaction: t,
      });
      if (!contact) {
        skipped += 1;
        continue;
      }

      const existing = await VotingEntity.findOne({
        where: {
          workspaceId,
          label: row.label,
          entityType: row.entityType,
        },
        transaction: t,
      });
      if (existing) {
        skipped += 1;
        continue;
      }

      const entity = await VotingEntity.create(
        {
          workspaceId,
          label: row.label,
          entityType: row.entityType,
          metadata: { weight: row.weight },
          isActive: true,
        },
        { transaction: t }
      );

      await VotingEntityOwner.create(
        {
          votingEntityId: entity.id,
          contactId: contact.id,
          isPrimary: true,
        },
        { transaction: t }
      );
      imported += 1;
    }

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

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