import { Op, type Transaction } from "sequelize";
import { initDb } from "@/lib/db";
import type {
  EligibilityModel,
  EligibilityStatus,
  VotingEntityType,
} from "@/types";

function recordDateCutoff(recordDate: string | null): Date | null {
  if (!recordDate) return null;
  const cutoff = new Date(`${recordDate}T23:59:59.999`);
  return Number.isNaN(cutoff.getTime()) ? null : cutoff;
}

function entityTypesForModel(model: EligibilityModel): VotingEntityType[] | null {
  switch (model) {
    case "property":
      return ["property_unit"];
    case "shareholder":
      return ["shareholder_account"];
    case "custom":
      return ["custom", "membership", "individual"];
    case "member":
    default:
      return null;
  }
}

function weightFromMetadata(
  metadata: Record<string, unknown> | null,
  model: EligibilityModel
): number {
  if (!metadata) return 1;
  const raw =
    metadata.weight ??
    metadata.shares ??
    metadata.shareCount ??
    metadata.voteWeight;
  const numeric = typeof raw === "number" ? raw : Number(raw);
  if (!Number.isFinite(numeric) || numeric <= 0) {
    return model === "shareholder" ? 1 : 1;
  }
  return Math.round(numeric * 10000) / 10000;
}

function isValidAsOf(
  record: {
    status: string;
    effectiveDate: string | null;
    expirationDate: string | null;
  },
  recordDate: string | null
): boolean {
  if (record.status !== "valid") return false;
  if (!recordDate) return true;
  if (record.effectiveDate && record.effectiveDate > recordDate) return false;
  if (record.expirationDate && record.expirationDate < recordDate) return false;
  return true;
}

/**
 * Snapshot eligible contacts/entities onto the voter roll as of recordDate.
 * Idempotent via unique (electionId, voterType, voterRefId).
 */
export async function generateVoterRollSnapshot(
  electionId: string,
  transaction: Transaction,
  options?: { includeContactsAfterRecordDate?: boolean }
): Promise<number> {
  const {
    Election,
    Contact,
    ElectionVoterRoll,
    VotingEntity,
    VotingEntityOwner,
    AuthorizationRecord,
  } = initDb();

  const election = await Election.findByPk(electionId, { transaction });
  if (!election) {
    throw new Error("NOT_FOUND");
  }

  const cutoff = recordDateCutoff(election.recordDate);
  let upserted = 0;

  if (election.eligibilityModel === "member") {
    const contactWhere: Record<string, unknown> = {
      workspaceId: election.workspaceId,
    };
    if (cutoff && !options?.includeContactsAfterRecordDate) {
      contactWhere.createdAt = { [Op.lte]: cutoff };
    }

    const contacts = await Contact.findAll({
      where: contactWhere,
      transaction,
    });

    for (const contact of contacts) {
      const changed = await upsertRollRow({
        ElectionVoterRoll,
        electionId,
        voterType: "contact",
        voterRefId: contact.id,
        weight: 1,
        eligibilityStatus: "eligible",
        transaction,
      });
      if (changed) upserted += 1;
    }

    return upserted;
  }

  const entityTypes = entityTypesForModel(election.eligibilityModel);
  const entityWhere: Record<string, unknown> = {
    workspaceId: election.workspaceId,
    isActive: true,
  };
  if (entityTypes) {
    entityWhere.entityType = { [Op.in]: entityTypes };
  }
  if (cutoff) {
    entityWhere.createdAt = { [Op.lte]: cutoff };
  }

  const entities = await VotingEntity.findAll({
    where: entityWhere,
    include: [
      {
        model: VotingEntityOwner,
        as: "owners",
        required: false,
      },
      {
        model: AuthorizationRecord,
        as: "authorizationRecords",
        required: false,
        separate: true,
        order: [["createdAt", "DESC"]],
      },
    ],
    transaction,
  });

  for (const entity of entities) {
    const owners =
      (entity.get("owners") as { contactId: string }[] | undefined) ?? [];
    const records =
      (entity.get("authorizationRecords") as
        | {
            status: string;
            effectiveDate: string | null;
            expirationDate: string | null;
            createdAt: Date;
          }[]
        | undefined) ?? [];

    let eligibilityStatus: EligibilityStatus = "eligible";
    if (election.requiresAuthorizationRecord && owners.length > 1) {
      const valid = records.find((record) =>
        isValidAsOf(record, election.recordDate)
      );
      if (!valid) {
        eligibilityStatus = "pending_authorization";
      }
    }

    const changed = await upsertRollRow({
      ElectionVoterRoll,
      electionId,
      voterType: "voting_entity",
      voterRefId: entity.id,
      weight: weightFromMetadata(entity.metadata, election.eligibilityModel),
      eligibilityStatus,
      transaction,
    });
    if (changed) upserted += 1;
  }

  return upserted;
}

async function upsertRollRow(input: {
  ElectionVoterRoll: ReturnType<typeof initDb>["ElectionVoterRoll"];
  electionId: string;
  voterType: "contact" | "voting_entity" | "user";
  voterRefId: string;
  weight: number;
  eligibilityStatus: EligibilityStatus;
  transaction: Transaction;
}): Promise<boolean> {
  const [row, created] = await input.ElectionVoterRoll.findOrCreate({
    where: {
      electionId: input.electionId,
      voterType: input.voterType,
      voterRefId: input.voterRefId,
    },
    defaults: {
      electionId: input.electionId,
      voterType: input.voterType,
      voterRefId: input.voterRefId,
      weight: input.weight,
      eligibilityStatus: input.eligibilityStatus,
      hasVoted: false,
    },
    transaction: input.transaction,
  });

  if (created) return true;

  if (!row.hasVoted) {
    const needsUpdate =
      row.eligibilityStatus !== input.eligibilityStatus ||
      Number(row.weight) !== Number(input.weight);
    if (needsUpdate) {
      await row.update(
        {
          eligibilityStatus: input.eligibilityStatus,
          weight: input.weight,
        },
        { transaction: input.transaction }
      );
      return true;
    }
  }

  return false;
}
