import { Op, fn, col } from "sequelize";
import { initDb } from "@/lib/db";
import { formatDomesticAnswer } from "@/lib/domestic/rules";
import type { DomesticQuestionVersion } from "@/lib/db/models/domestic-question-version";
import type {
  DomesticConditionConfig,
  DomesticConditionType,
  DomesticEdgeType,
  DomesticOptionsConfig,
  DomesticQuestionStatus,
  DomesticQuestionType,
  DomesticSensitivity,
} from "@/types/participant";

export interface DomesticCategoryRow {
  id: string;
  key: string;
  name: string;
  description: string | null;
  isActive: boolean;
  questionCount: number;
}

export async function getDomesticCategories(): Promise<DomesticCategoryRow[]> {
  const { DomesticCategory, DomesticQuestion } = initDb();
  const [categories, counts] = await Promise.all([
    DomesticCategory.findAll({ order: [["sortOrder", "ASC"], ["name", "ASC"]] }),
    DomesticQuestion.findAll({
      attributes: ["categoryId", [fn("COUNT", col("id")), "n"]],
      group: ["categoryId"],
      raw: true,
    }) as unknown as Promise<Array<{ categoryId: string; n: number }>>,
  ]);
  const byCategory = new Map(counts.map((c) => [c.categoryId, Number(c.n)]));
  return categories.map((c) => ({
    id: c.id,
    key: c.key,
    name: c.name,
    description: c.description ?? null,
    isActive: c.isActive,
    questionCount: byCategory.get(c.id) ?? 0,
  }));
}

export interface DomesticQuestionRow {
  id: string;
  code: string;
  categoryName: string;
  status: DomesticQuestionStatus;
  type: DomesticQuestionType | null;
  prompt: string;
  versionNumber: number | null;
  isEntry: boolean;
  rotationWeight: number;
  answerCount: number;
  ruleCount: number;
}

export async function getDomesticQuestions(filters: {
  status?: string;
  categoryId?: string;
  search?: string;
}): Promise<DomesticQuestionRow[]> {
  const {
    DomesticQuestion,
    DomesticQuestionVersion,
    DomesticCategory,
    DomesticAnswer,
    DomesticQuestionEdge,
  } = initDb();

  const where: Record<string | symbol, unknown> = {};
  if (filters.status) where.status = filters.status;
  if (filters.categoryId) where.categoryId = filters.categoryId;
  if (filters.search) {
    where[Op.or] = [
      { code: { [Op.like]: `%${filters.search}%` } },
      { "$currentVersion.prompt$": { [Op.like]: `%${filters.search}%` } },
    ];
  }

  const questions = await DomesticQuestion.findAll({
    where,
    include: [
      { model: DomesticCategory, as: "category", attributes: ["name"] },
      {
        model: DomesticQuestionVersion,
        as: "currentVersion",
        attributes: ["id", "type", "prompt", "versionNumber"],
      },
    ],
    order: [["code", "ASC"]],
  });

  const ids = questions.map((q) => q.id);
  const versionIds = questions
    .map((q) => q.currentVersionId)
    .filter((v): v is string => Boolean(v));

  const [answerCounts, ruleCounts] = await Promise.all([
    ids.length
      ? (DomesticAnswer.findAll({
          where: { domesticQuestionId: { [Op.in]: ids } },
          attributes: ["domesticQuestionId", [fn("COUNT", col("id")), "n"]],
          group: ["domesticQuestionId"],
          raw: true,
        }) as unknown as Promise<Array<{ domesticQuestionId: string; n: number }>>)
      : Promise.resolve([]),
    versionIds.length
      ? (DomesticQuestionEdge.findAll({
          where: {
            fromQuestionVersionId: { [Op.in]: versionIds },
            effectiveTo: null,
          },
          attributes: ["fromQuestionVersionId", [fn("COUNT", col("id")), "n"]],
          group: ["fromQuestionVersionId"],
          raw: true,
        }) as unknown as Promise<Array<{ fromQuestionVersionId: string; n: number }>>)
      : Promise.resolve([]),
  ]);

  const answersBy = new Map(answerCounts.map((a) => [a.domesticQuestionId, Number(a.n)]));
  const rulesBy = new Map(ruleCounts.map((r) => [r.fromQuestionVersionId, Number(r.n)]));

  return questions.map((q) => {
    const row = q as typeof q & {
      category?: { name: string };
      currentVersion?: DomesticQuestionVersion;
    };
    return {
      id: q.id,
      code: q.code,
      categoryName: row.category?.name ?? "—",
      status: q.status,
      type: row.currentVersion?.type ?? null,
      prompt: row.currentVersion?.prompt ?? "",
      versionNumber: row.currentVersion?.versionNumber ?? null,
      isEntry: q.isEntry,
      rotationWeight: q.rotationWeight,
      answerCount: answersBy.get(q.id) ?? 0,
      ruleCount: q.currentVersionId ? (rulesBy.get(q.currentVersionId) ?? 0) : 0,
    };
  });
}

export interface DomesticEdgeView {
  id: string;
  conditionType: DomesticConditionType;
  conditionConfig: DomesticConditionConfig | null;
  edgeType: DomesticEdgeType;
  priority: number;
  toQuestionId: string | null;
  toCode: string | null;
  toPrompt: string | null;
}

export interface DomesticQuestionDetail {
  id: string;
  code: string;
  categoryId: string;
  status: DomesticQuestionStatus;
  sensitivity: DomesticSensitivity;
  isEntry: boolean;
  rotationWeight: number;
  current: {
    id: string;
    versionNumber: number;
    type: DomesticQuestionType;
    prompt: string;
    helpText: string | null;
    purpose: string | null;
    optionsConfig: DomesticOptionsConfig | null;
    published: boolean;
  } | null;
  versions: Array<{
    id: string;
    versionNumber: number;
    prompt: string;
    publishedAt: string | null;
    retiredAt: string | null;
    answerCount: number;
  }>;
  edges: DomesticEdgeView[];
  incoming: Array<{ questionId: string; code: string; edgeType: DomesticEdgeType }>;
  answerCount: number;
  distribution: Array<{ label: string; count: number }>;
}

export async function getDomesticQuestionDetail(
  questionId: string
): Promise<DomesticQuestionDetail | null> {
  const {
    DomesticQuestion,
    DomesticQuestionVersion,
    DomesticQuestionEdge,
    DomesticAnswer,
  } = initDb();

  const question = await DomesticQuestion.findByPk(questionId);
  if (!question) return null;

  const versions = await DomesticQuestionVersion.findAll({
    where: { domesticQuestionId: question.id },
    order: [["versionNumber", "DESC"]],
  });
  const current =
    versions.find((v) => v.id === question.currentVersionId) ?? versions[0] ?? null;

  const versionAnswerCounts = (await DomesticAnswer.findAll({
    where: { domesticQuestionId: question.id },
    attributes: ["questionVersionId", [fn("COUNT", col("id")), "n"]],
    group: ["questionVersionId"],
    raw: true,
  })) as unknown as Array<{ questionVersionId: string; n: number }>;
  const countsByVersion = new Map(
    versionAnswerCounts.map((c) => [c.questionVersionId, Number(c.n)])
  );

  const edges = current
    ? await DomesticQuestionEdge.findAll({
        where: { fromQuestionVersionId: current.id, effectiveTo: null },
        include: [
          {
            model: DomesticQuestion,
            as: "toQuestion",
            attributes: ["id", "code", "currentVersionId"],
          },
        ],
        order: [["priority", "ASC"]],
      })
    : [];

  const targetVersionIds = edges
    .map((e) => (e as typeof e & { toQuestion?: { currentVersionId: string | null } }).toQuestion?.currentVersionId)
    .filter((v): v is string => Boolean(v));
  const targetVersions = targetVersionIds.length
    ? await DomesticQuestionVersion.findAll({
        where: { id: { [Op.in]: targetVersionIds } },
        attributes: ["id", "prompt"],
      })
    : [];
  const promptByVersion = new Map(targetVersions.map((v) => [v.id, v.prompt]));

  const incomingEdges = await DomesticQuestionEdge.findAll({
    where: { toQuestionId: question.id, effectiveTo: null },
    include: [
      {
        model: DomesticQuestionVersion,
        as: "fromVersion",
        attributes: ["id", "domesticQuestionId"],
        include: [{ model: DomesticQuestion, as: "question", attributes: ["id", "code", "currentVersionId"] }],
      },
    ],
  });

  let distribution: Array<{ label: string; count: number }> = [];
  if (current && ["single_choice", "multi_choice", "yes_no", "rating"].includes(current.type)) {
    const answers = await DomesticAnswer.findAll({
      where: { questionVersionId: current.id },
      attributes: ["value"],
      limit: 5000,
    });
    const tally = new Map<string, number>();
    for (const a of answers) {
      const values = Array.isArray(a.value) ? a.value : [a.value];
      for (const v of values) {
        const label = formatDomesticAnswer(current.type, current.optionsConfig, v);
        tally.set(label, (tally.get(label) ?? 0) + 1);
      }
    }
    distribution = Array.from(tally.entries())
      .map(([label, count]) => ({ label, count }))
      .sort((a, b) => b.count - a.count);
  }

  const totalAnswers = Array.from(countsByVersion.values()).reduce((s, n) => s + n, 0);

  return {
    id: question.id,
    code: question.code,
    categoryId: question.categoryId,
    status: question.status,
    sensitivity: question.sensitivity,
    isEntry: question.isEntry,
    rotationWeight: question.rotationWeight,
    current: current
      ? {
          id: current.id,
          versionNumber: current.versionNumber,
          type: current.type,
          prompt: current.prompt,
          helpText: current.helpText ?? null,
          purpose: current.purpose ?? null,
          optionsConfig: current.optionsConfig ?? null,
          published: Boolean(current.publishedAt),
        }
      : null,
    versions: versions.map((v) => ({
      id: v.id,
      versionNumber: v.versionNumber,
      prompt: v.prompt,
      publishedAt: v.publishedAt?.toISOString() ?? null,
      retiredAt: v.retiredAt?.toISOString() ?? null,
      answerCount: countsByVersion.get(v.id) ?? 0,
    })),
    edges: edges.map((e) => {
      const target = (e as typeof e & {
        toQuestion?: { id: string; code: string; currentVersionId: string | null };
      }).toQuestion;
      return {
        id: e.id,
        conditionType: e.conditionType,
        conditionConfig: e.conditionConfig ?? null,
        edgeType: e.edgeType,
        priority: e.priority,
        toQuestionId: e.toQuestionId ?? null,
        toCode: target?.code ?? null,
        toPrompt: target?.currentVersionId
          ? (promptByVersion.get(target.currentVersionId) ?? null)
          : null,
      };
    }),
    incoming: incomingEdges
      .map((e) => {
        const from = (e as typeof e & {
          fromVersion?: {
            id: string;
            question?: { id: string; code: string; currentVersionId: string | null };
          };
        }).fromVersion;
        // Only edges hanging off the source's current version are live.
        if (!from?.question || from.question.currentVersionId !== from.id) return null;
        return {
          questionId: from.question.id,
          code: from.question.code,
          edgeType: e.edgeType,
        };
      })
      .filter((x): x is NonNullable<typeof x> => Boolean(x)),
    answerCount: totalAnswers,
    distribution,
  };
}

export interface DomesticAnswerAdminRow {
  id: string;
  answeredAt: string;
  answerText: string;
  versionNumber: number;
  versionId: string;
  participantLabel: string;
  revision: number;
}

export async function getDomesticQuestionAnswers(
  questionId: string,
  opts?: { versionId?: string; limit?: number; offset?: number }
): Promise<{ rows: DomesticAnswerAdminRow[]; total: number }> {
  const { DomesticAnswer, DomesticQuestionVersion, ParticipantProfile } = initDb();
  const limit = Math.min(Math.max(opts?.limit ?? 100, 1), 500);
  const offset = Math.max(opts?.offset ?? 0, 0);
  const where: Record<string, string> = { domesticQuestionId: questionId };
  if (opts?.versionId) where.questionVersionId = opts.versionId;

  const { count, rows } = await DomesticAnswer.findAndCountAll({
    where,
    include: [
      {
        model: DomesticQuestionVersion,
        as: "version",
        attributes: ["id", "versionNumber", "type", "optionsConfig"],
      },
      {
        model: ParticipantProfile,
        as: "participantProfile",
        attributes: ["id", "countryCode", "regionCode", "ageBand"],
      },
    ],
    order: [["answeredAt", "DESC"]],
    limit,
    offset,
  });

  const formatted = rows.map((r) => {
    const row = r as typeof r & {
      version?: DomesticQuestionVersion;
      participantProfile?: {
        id: string;
        countryCode: string | null;
        regionCode: string | null;
        ageBand: string | null;
      };
    };
    const profile = row.participantProfile;
    const profileBits = [
      profile?.countryCode,
      profile?.regionCode,
      profile?.ageBand,
    ].filter(Boolean);
    const idShort = profile?.id ? profile.id.slice(0, 8) : "—";
    return {
      id: r.id,
      answeredAt: r.answeredAt.toISOString(),
      answerText: row.version
        ? formatDomesticAnswer(row.version.type, row.version.optionsConfig, r.value)
        : String(r.value ?? "—"),
      versionNumber: row.version?.versionNumber ?? 0,
      versionId: r.questionVersionId,
      participantLabel:
        profileBits.length > 0
          ? `${profileBits.join(" · ")} (${idShort}…)`
          : `Participant ${idShort}…`,
      revision: r.revision,
    };
  });

  return { rows: formatted, total: count };
}

export async function getDomesticTargetOptions(excludeId?: string) {
  const { DomesticQuestion, DomesticQuestionVersion } = initDb();
  const questions = await DomesticQuestion.findAll({
    where: {
      status: { [Op.ne]: "retired" },
      ...(excludeId ? { id: { [Op.ne]: excludeId } } : {}),
    },
    include: [
      { model: DomesticQuestionVersion, as: "currentVersion", attributes: ["prompt"] },
    ],
    order: [["code", "ASC"]],
  });
  return questions.map((q) => ({
    id: q.id,
    code: q.code,
    status: q.status,
    prompt:
      (q as typeof q & { currentVersion?: { prompt: string } }).currentVersion?.prompt ?? "",
  }));
}
