import { z } from "zod";

export const electionStatusSchema = z.enum([
  "draft",
  "open",
  "closed",
  "certified",
]);

export const ballotSecrecySchema = z.enum(["secret", "open"]);

export const votingMethodSchema = z.enum([
  "plurality",
  "ranked_choice_irv",
  "single_transferable_vote",
  "approval",
  "condorcet",
  "cumulative",
  "weighted",
]);

export const eligibilityModelSchema = z.enum([
  "member",
  "property",
  "shareholder",
  "custom",
]);

export const resultsVisibilitySchema = z.enum([
  "after_close",
  "admin_realtime",
]);

export const electionPositionTypeSchema = z.enum([
  "single_seat",
  "multi_seat",
  "ballot_measure",
]);

const dateOnlySchema = z
  .string()
  .trim()
  .optional()
  .nullable()
  .transform((value) => {
    if (!value) return null;
    return value.slice(0, 10);
  })
  .refine(
    (value) => value === null || /^\d{4}-\d{2}-\d{2}$/.test(value),
    "Invalid record date"
  );

const optionalDateSchema = z
  .string()
  .trim()
  .optional()
  .nullable()
  .transform((value) => {
    if (!value) return null;
    const parsed = new Date(value);
    return Number.isNaN(parsed.getTime()) ? null : parsed;
  });

export const wizardCandidateSchema = z.object({
  name: z.string().trim().min(1).max(255),
  biography: z.string().trim().max(20000).optional().nullable(),
  goalsStatement: z.string().trim().max(20000).optional().nullable(),
});

export const wizardPositionSchema = z.object({
  title: z.string().trim().min(1).max(500),
  positionType: electionPositionTypeSchema.default("single_seat"),
  seatsAvailable: z.number().int().min(1).max(100).default(1),
  candidates: z.array(wizardCandidateSchema).min(1),
});

export const createElectionSchema = z
  .object({
    title: z.string().trim().min(1).max(500),
    description: z.string().trim().max(20000).optional().nullable(),
    ballotSecrecy: ballotSecrecySchema.default("open"),
    votingMethod: votingMethodSchema.default("plurality"),
    eligibilityModel: eligibilityModelSchema.default("member"),
    requiresAuthorizationRecord: z.boolean().default(false),
    recordDate: dateOnlySchema,
    quorumRequired: z.boolean().default(false),
    quorumCount: z.number().int().min(1).optional().nullable(),
    digitalSignatureRequired: z.boolean().default(false),
    verificationMethod: z
      .enum(["email", "sms_2fa", "email_sms"])
      .default("email"),
    resultsVisibility: resultsVisibilitySchema.default("after_close"),
    isTestMode: z.boolean().default(false),
    openEnrollment: z.boolean().default(false),
    startAt: optionalDateSchema,
    endAt: optionalDateSchema,
    positions: z.array(wizardPositionSchema).default([]),
  })
  .refine(
    (data) => {
      if (!data.recordDate || !data.startAt) return true;
      return new Date(`${data.recordDate}T00:00:00`).getTime() <= data.startAt.getTime();
    },
    { message: "Record date must be on or before the start date", path: ["recordDate"] }
  )
  .refine(
    (data) => {
      if (!data.startAt || !data.endAt) return true;
      return data.startAt.getTime() <= data.endAt.getTime();
    },
    { message: "End date must be on or after the start date", path: ["endAt"] }
  )
  .refine(
    (data) => !data.quorumRequired || (data.quorumCount != null && data.quorumCount > 0),
    { message: "Quorum count is required when quorum is enabled", path: ["quorumCount"] }
  );

export const updateElectionSchema = z
  .object({
    electionId: z.string().uuid(),
    title: z.string().trim().min(1).max(500).optional(),
    description: z.string().trim().max(20000).optional().nullable(),
    ballotSecrecy: ballotSecrecySchema.optional(),
    votingMethod: votingMethodSchema.optional(),
    eligibilityModel: eligibilityModelSchema.optional(),
    requiresAuthorizationRecord: z.boolean().optional(),
    recordDate: z.string().trim().optional().nullable(),
    quorumRequired: z.boolean().optional(),
    quorumCount: z.number().int().min(1).optional().nullable(),
    digitalSignatureRequired: z.boolean().optional(),
    verificationMethod: z.enum(["email", "sms_2fa", "email_sms"]).optional(),
    resultsVisibility: resultsVisibilitySchema.optional(),
    isTestMode: z.boolean().optional(),
    openEnrollment: z.boolean().optional(),
    startAt: z.string().trim().optional().nullable(),
    endAt: z.string().trim().optional().nullable(),
    positions: z.array(wizardPositionSchema).optional(),
  })
  .refine(
    (data) => {
      if (!data.recordDate || !data.startAt) return true;
      const record = new Date(`${data.recordDate.slice(0, 10)}T00:00:00`);
      const start = new Date(data.startAt);
      if (Number.isNaN(record.getTime()) || Number.isNaN(start.getTime())) {
        return false;
      }
      return record.getTime() <= start.getTime();
    },
    { message: "Record date must be on or before the start date", path: ["recordDate"] }
  )
  .refine(
    (data) => {
      if (!data.startAt || !data.endAt) return true;
      const start = new Date(data.startAt);
      const end = new Date(data.endAt);
      if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
        return false;
      }
      return start.getTime() <= end.getTime();
    },
    { message: "End date must be on or after the start date", path: ["endAt"] }
  )
  .refine(
    (data) =>
      data.quorumRequired !== true ||
      (data.quorumCount != null && data.quorumCount > 0),
    { message: "Quorum count is required when quorum is enabled", path: ["quorumCount"] }
  );

export const electionIdSchema = z.object({
  electionId: z.string().uuid(),
});

export const certifyElectionSchema = z
  .object({
    electionId: z.string().uuid(),
    overridePendingAuthorization: z.boolean().default(false),
    overrideReason: z.string().trim().max(2000).optional().nullable(),
  })
  .refine(
    (data) =>
      !data.overridePendingAuthorization ||
      (data.overrideReason != null && data.overrideReason.trim().length > 0),
    {
      message: "Override reason is required when certifying with pending rows",
      path: ["overrideReason"],
    }
  );

export const setElectionTestModeSchema = z.object({
  electionId: z.string().uuid(),
  isTestMode: z.boolean(),
});

export const setElectionOpenEnrollmentSchema = z.object({
  electionId: z.string().uuid(),
  openEnrollment: z.boolean(),
});

/** Schedule (open/close) can be changed after publish, including live elections. */
export const updateElectionScheduleSchema = z
  .object({
    electionId: z.string().uuid(),
    startAt: z.string().trim().optional().nullable(),
    endAt: z.string().trim().optional().nullable(),
  })
  .refine(
    (data) => data.startAt !== undefined || data.endAt !== undefined,
    { message: "Provide an open or close date to update" }
  )
  .refine(
    (data) => {
      if (!data.startAt || !data.endAt) return true;
      const start = new Date(data.startAt);
      const end = new Date(data.endAt);
      if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
        return false;
      }
      return start.getTime() <= end.getTime();
    },
    { message: "Close date must be on or after the open date", path: ["endAt"] }
  );

export type CreateElectionInput = z.infer<typeof createElectionSchema>;
export type UpdateElectionInput = z.infer<typeof updateElectionSchema>;
export type UpdateElectionScheduleInput = z.infer<
  typeof updateElectionScheduleSchema
>;
