import { Op } from "sequelize";
import { initDb } from "@/lib/db";
import type { Election } from "@/lib/db/models/election";
import type { Workspace } from "@/lib/db/models/workspace";
import type { ElectionStatus } from "@/types";

export async function getScopedElection(electionId: string, workspaceId: string) {
  const { Election } = initDb();
  return Election.findOne({ where: { id: electionId, workspaceId } });
}

export interface ElectionListFilters {
  status?: ElectionStatus | "all";
  search?: string;
  includeTest?: boolean;
}

export interface ElectionListRow {
  id: string;
  title: string;
  status: ElectionStatus;
  isTestMode: boolean;
  voteCount: number;
  createdAt: Date;
  startAt: Date | null;
  endAt: Date | null;
}

/** List elections for a workspace. Test-mode rows are included for management. */
export async function getWorkspaceElections(
  workspaceId: string,
  filters: ElectionListFilters = {}
): Promise<ElectionListRow[]> {
  const { Election, Ballot } = initDb();

  const where: Record<string, unknown> = { workspaceId };

  if (filters.status && filters.status !== "all") {
    where.status = filters.status;
  }

  if (filters.search?.trim()) {
    where.title = { [Op.like]: `%${filters.search.trim()}%` };
  }

  if (filters.includeTest === false) {
    where.isTestMode = false;
  }

  const elections = await Election.findAll({
    where,
    order: [["createdAt", "DESC"]],
    include: [
      {
        model: Ballot,
        as: "ballots",
        attributes: ["id"],
        required: false,
      },
    ],
  });

  return elections.map((election) => {
    const ballots = election.get("ballots") as { id: string }[] | undefined;
    return {
      id: election.id,
      title: election.title,
      status: election.status,
      isTestMode: election.isTestMode,
      voteCount: ballots?.length ?? 0,
      createdAt: election.createdAt,
      startAt: election.startAt,
      endAt: election.endAt,
    };
  });
}

export interface ElectionKpis {
  eligibleVoters: number;
  votesCast: number;
  outstanding: number;
  turnoutPercent: number;
  quorumRequired: boolean;
  quorumCount: number | null;
  quorumMet: boolean;
}

export async function getElectionKpis(electionId: string): Promise<ElectionKpis> {
  const { Election, ElectionVoterRoll, Ballot } = initDb();

  const election = await Election.findByPk(electionId);
  if (!election) {
    return {
      eligibleVoters: 0,
      votesCast: 0,
      outstanding: 0,
      turnoutPercent: 0,
      quorumRequired: false,
      quorumCount: null,
      quorumMet: false,
    };
  }

  const [eligibleVoters, votesCast] = await Promise.all([
    ElectionVoterRoll.count({
      where: { electionId, eligibilityStatus: "eligible" },
    }),
    Ballot.count({ where: { electionId } }),
  ]);

  const outstanding = Math.max(0, eligibleVoters - votesCast);
  const turnoutPercent =
    eligibleVoters === 0 ? 0 : Math.round((votesCast / eligibleVoters) * 1000) / 10;
  const quorumMet =
    !election.quorumRequired ||
    (election.quorumCount != null && votesCast >= election.quorumCount);

  return {
    eligibleVoters,
    votesCast,
    outstanding,
    turnoutPercent,
    quorumRequired: election.quorumRequired,
    quorumCount: election.quorumCount,
    quorumMet,
  };
}

export async function getElectionDetail(electionId: string, workspaceId: string) {
  const election = await getScopedElection(electionId, workspaceId);
  if (!election) return null;

  const kpis = await getElectionKpis(election.id);
  return { election, kpis };
}

export function appBaseUrl(): string {
  return (
    process.env.NEXT_PUBLIC_APP_URL ??
    process.env.NEXTAUTH_URL ??
    "http://localhost:3000"
  );
}

export interface AdminElectionRow {
  id: string;
  title: string;
  status: ElectionStatus;
  workspaceName: string;
  isTestMode: boolean;
  voteCount: number;
  createdAt: Date;
}

export async function getAdminElections(filters: {
  status?: string;
  search?: string;
} = {}): Promise<AdminElectionRow[]> {
  const { Election, Workspace, Ballot } = initDb();

  const where: Record<string, unknown> = {};
  if (filters.status && filters.status !== "all") {
    where.status = filters.status;
  }
  if (filters.search?.trim()) {
    where.title = { [Op.like]: `%${filters.search.trim()}%` };
  }

  const elections = await Election.findAll({
    where,
    order: [["createdAt", "DESC"]],
    limit: 100,
    include: [
      {
        model: Workspace,
        as: "workspace",
        attributes: ["name"],
      },
      {
        model: Ballot,
        as: "ballots",
        attributes: ["id"],
        required: false,
      },
    ],
  });

  return elections.map((election) => {
    const workspace = election.get("workspace") as Workspace | undefined;
    const ballots = election.get("ballots") as { id: string }[] | undefined;
    return {
      id: election.id,
      title: election.title,
      status: election.status,
      workspaceName: workspace?.name ?? "—",
      isTestMode: election.isTestMode,
      voteCount: ballots?.length ?? 0,
      createdAt: election.createdAt,
    };
  });
}

export function serializeElection(election: Election) {
  return {
    id: election.id,
    workspaceId: election.workspaceId,
    title: election.title,
    description: election.description,
    slug: election.slug,
    status: election.status,
    ballotSecrecy: election.ballotSecrecy,
    votingMethod: election.votingMethod,
    eligibilityModel: election.eligibilityModel,
    allowProxyVoting: election.allowProxyVoting,
    requiresAuthorizationRecord: election.requiresAuthorizationRecord,
    recordDate: election.recordDate,
    quorumRequired: election.quorumRequired,
    quorumCount: election.quorumCount,
    digitalSignatureRequired: election.digitalSignatureRequired,
    resultsVisibility: election.resultsVisibility,
    isTestMode: election.isTestMode,
    openEnrollment: election.openEnrollment,
    startAt: election.startAt,
    endAt: election.endAt,
    publishedAt: election.publishedAt,
    closedAt: election.closedAt,
    certifiedAt: election.certifiedAt,
    createdAt: election.createdAt,
  };
}
