import { Op } from "sequelize";
import { initDb } from "@/lib/db";
import type { Question } from "@/lib/db/models/question";
import type { Answer } from "@/lib/db/models/answer";
import type { Respondent } from "@/lib/db/models/respondent";
import type { Collector } from "@/lib/db/models/collector";
import { getScopedSurvey } from "@/lib/queries/survey-queries";
import {
  extractAnswerScalars,
  formatAnswerDisplay,
  formatDuration,
  collectorTypeLabel,
  tokenizeWords,
} from "@/lib/utils/analytics-format";
import { parseJsonColumn } from "@/lib/utils/parse-json-column";
import { isMultiSelectDropdown } from "@/lib/utils/dropdown-select";
import type { QuestionOptionsConfig } from "@/types";
import type {
  AnalyticsFilters,
  AnalyticsDateRange,
  AnalyticsStatusFilter,
  SurveyAnalytics,
  QuestionAnalytics,
  RespondentDetail,
  TimeSeriesPoint,
  TrafficSourcePoint,
} from "@/types/analytics";

function parseDateRange(dateRange: AnalyticsDateRange): Date | null {
  if (dateRange === "all") return null;
  const days = dateRange === "7d" ? 7 : dateRange === "30d" ? 30 : 90;
  const from = new Date();
  from.setDate(from.getDate() - days);
  from.setHours(0, 0, 0, 0);
  return from;
}

function buildRespondentWhere(
  surveyId: string,
  filters: AnalyticsFilters
): Record<string, unknown> {
  const where: Record<string, unknown> = { surveyId };

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

  const from = parseDateRange(filters.dateRange);
  if (from) {
    where.startedAt = { [Op.gte]: from };
  }

  return where;
}

const VALID_RANGES: AnalyticsDateRange[] = ["7d", "30d", "90d", "all"];
const VALID_STATUSES: AnalyticsStatusFilter[] = [
  "all",
  "completed",
  "in_progress",
  "abandoned",
];

/** Parse URL search params into analytics filters. */
export function parseAnalyticsFilters(
  searchParams: Record<string, string | string[] | undefined>
): AnalyticsFilters {
  const rawRange = String(searchParams.range ?? "all");
  const rawStatus = String(searchParams.status ?? "all");

  return {
    dateRange: VALID_RANGES.includes(rawRange as AnalyticsDateRange)
      ? (rawRange as AnalyticsDateRange)
      : "30d",
    status: VALID_STATUSES.includes(rawStatus as AnalyticsStatusFilter)
      ? (rawStatus as AnalyticsStatusFilter)
      : "all",
  };
}

function dateKey(d: Date): string {
  return d.toISOString().slice(0, 10);
}

function computeNpsScore(scores: number[]): number {
  if (scores.length === 0) return 0;
  const promoters = scores.filter((s) => s >= 9).length;
  const detractors = scores.filter((s) => s <= 6).length;
  return Math.round(((promoters - detractors) / scores.length) * 100);
}

function buildChoiceAnalytics(
  question: Question,
  answers: Answer[]
): QuestionAnalytics {
  const optionsConfig = parseJsonColumn<QuestionOptionsConfig>(
    question.optionsConfig
  );
  const options = optionsConfig?.choices ?? [];
  const counts = new Map<string, number>();

  for (const opt of options) {
    counts.set(opt, 0);
  }

  let responseCount = 0;

  for (const ans of answers) {
    const scalars = extractAnswerScalars(ans.value, question.type);
    if (scalars.length === 0) continue;
    responseCount++;
    for (const label of scalars) {
      counts.set(label, (counts.get(label) ?? 0) + 1);
    }
  }

  const totalSelections =
    Array.from(counts.values()).reduce((a, b) => a + b, 0) || 1;
  const multi =
    question.type === "checkbox" ||
    isMultiSelectDropdown(question.type, optionsConfig);
  const percentBase = multi ? Math.max(responseCount, 1) : totalSelections;

  const choices = Array.from(counts.entries())
    .map(([label, count]) => ({
      label,
      count,
      percent: Math.round((count / percentBase) * 1000) / 10,
    }))
    .sort((a, b) => b.count - a.count);

  return {
    kind: "choice",
    questionType: question.type as "multiple_choice" | "checkbox" | "dropdown",
    questionId: question.id,
    title: question.title,
    position: question.position,
    responseCount,
    choices,
    allowMultiple: multi,
  };
}

function buildScoreAnalytics(
  question: Question,
  answers: Answer[],
  kind: "nps" | "rating"
): QuestionAnalytics {
  const scale = parseJsonColumn<QuestionOptionsConfig>(question.optionsConfig);
  const min = kind === "nps" ? 0 : (scale?.minRating ?? 1);
  const max = kind === "nps" ? 10 : (scale?.maxRating ?? 5);

  const distribution = Array.from({ length: Math.max(1, max - min + 1) }, (_, i) => ({
    score: min + i,
    count: 0,
  }));

  const scores: number[] = [];

  for (const ans of answers) {
    const scalars = extractAnswerScalars(ans.value, question.type);
    if (scalars.length === 0) continue;
    const score = Number(scalars[0]);
    if (!Number.isFinite(score)) continue;
    scores.push(score);
    const idx = score - min;
    if (idx >= 0 && idx < distribution.length) {
      distribution[idx].count++;
    }
  }

  if (kind === "nps") {
    return {
      kind: "nps",
      questionId: question.id,
      title: question.title,
      position: question.position,
      responseCount: scores.length,
      distribution,
      npsScore: computeNpsScore(scores),
    };
  }

  const average =
    scores.length > 0
      ? Math.round((scores.reduce((a, b) => a + b, 0) / scores.length) * 10) /
        10
      : 0;

  return {
    kind: "rating",
    questionId: question.id,
    title: question.title,
    position: question.position,
    responseCount: scores.length,
    distribution,
    average,
  };
}

function buildOpenTextAnalytics(
  question: Question,
  answers: Answer[]
): QuestionAnalytics {
  const texts: string[] = [];
  const newestFirst = [...answers].sort(
    (a, b) =>
      new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
  );

  for (const ans of newestFirst) {
    const scalars = extractAnswerScalars(ans.value, question.type);
    if (scalars[0]) texts.push(scalars[0]);
  }

  return {
    kind: "open_text",
    questionId: question.id,
    title: question.title,
    position: question.position,
    responseCount: texts.length,
    words: tokenizeWords(texts),
    samples: texts.slice(0, 8),
  };
}

function buildResponsesOverTime(respondents: Respondent[]): TimeSeriesPoint[] {
  const map = new Map<string, number>();

  for (const r of respondents) {
    if (r.status !== "completed" || !r.completedAt) continue;
    const key = dateKey(r.completedAt);
    map.set(key, (map.get(key) ?? 0) + 1);
  }

  return Array.from(map.entries())
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([date, count]) => ({ date, count }));
}

function buildTrafficSources(respondents: Respondent[]): TrafficSourcePoint[] {
  const map = new Map<string, number>();

  for (const r of respondents) {
    const collector = r.get("collector") as Collector | null | undefined;
    const label = collectorTypeLabel(collector?.type);
    map.set(label, (map.get(label) ?? 0) + 1);
  }

  return Array.from(map.entries())
    .sort((a, b) => b[1] - a[1])
    .map(([name, value]) => ({ name, value }));
}

/** Aggregate survey analytics scoped to workspace. */
export async function getSurveyAnalytics(
  surveyId: string,
  workspaceId: string,
  filters: AnalyticsFilters
): Promise<SurveyAnalytics | null> {
  const survey = await getScopedSurvey(surveyId, workspaceId);
  if (!survey) return null;

  const { Respondent, Collector, Answer } = initDb();
  const questions = (survey.get("questions") as Question[] | undefined) ?? [];

  const respondents = await Respondent.findAll({
    where: buildRespondentWhere(surveyId, filters),
    include: [
      {
        model: Collector,
        as: "collector",
        attributes: ["type"],
        required: false,
      },
    ],
    order: [["startedAt", "DESC"]],
  });

  const totalViews = respondents.length;
  const completed = respondents.filter((r) => r.status === "completed");
  const responses = completed.length;
  const completionRate =
    totalViews > 0 ? Math.round((responses / totalViews) * 1000) / 10 : 0;

  const totalSeconds = completed.reduce(
    (sum, r) => sum + (r.timeToCompleteSeconds ?? 0),
    0
  );
  const avgSeconds =
    responses > 0 ? Math.round(totalSeconds / responses) : 0;

  const respondentIds = respondents.map((r) => r.id);
  const allAnswers =
    respondentIds.length > 0
      ? await Answer.findAll({
          where: { respondentId: { [Op.in]: respondentIds } },
        })
      : [];

  const answersByQuestion = new Map<string, Answer[]>();
  for (const ans of allAnswers) {
    const list = answersByQuestion.get(ans.questionId) ?? [];
    list.push(ans);
    answersByQuestion.set(ans.questionId, list);
  }

  const questionAnalytics: QuestionAnalytics[] = questions.map((q) => {
    const qAnswers = answersByQuestion.get(q.id) ?? [];

    if (
      q.type === "multiple_choice" ||
      q.type === "checkbox" ||
      q.type === "dropdown"
    ) {
      return buildChoiceAnalytics(q, qAnswers);
    }
    if (q.type === "nps") {
      return buildScoreAnalytics(q, qAnswers, "nps");
    }
    if (q.type === "rating") {
      return buildScoreAnalytics(q, qAnswers, "rating");
    }
    if (q.type === "open_text") {
      return buildOpenTextAnalytics(q, qAnswers);
    }

    return {
      kind: "other",
      questionId: q.id,
      title: q.title,
      position: q.position,
      responseCount: qAnswers.filter((a) => a.value != null).length,
    };
  });

  return {
    surveyId: survey.id,
    surveyTitle: survey.title,
    overview: {
      totalViews,
      responses,
      completionRate,
      avgTimeToComplete: formatDuration(avgSeconds),
    },
    responsesOverTime: buildResponsesOverTime(respondents),
    trafficSources: buildTrafficSources(respondents),
    questions: questionAnalytics,
    respondents: respondents.map((r) => ({
      id: r.id,
      status: r.status,
      startedAt: r.startedAt,
      completedAt: r.completedAt,
      timeToCompleteSeconds: r.timeToCompleteSeconds,
    })),
  };
}

/** Fetch a single respondent's answers for the detail view. */
export async function getRespondentDetail(
  surveyId: string,
  respondentId: string,
  workspaceId: string
): Promise<RespondentDetail | null> {
  const survey = await getScopedSurvey(surveyId, workspaceId);
  if (!survey) return null;

  const { Respondent, Answer } = initDb();
  const questions = (survey.get("questions") as Question[] | undefined) ?? [];

  const respondent = await Respondent.findOne({
    where: { id: respondentId, surveyId },
  });

  if (!respondent) return null;

  const answers = await Answer.findAll({
    where: { respondentId },
  });

  const answerMap = new Map(answers.map((a) => [a.questionId, a]));

  return {
    id: respondent.id,
    status: respondent.status,
    startedAt: respondent.startedAt,
    completedAt: respondent.completedAt,
    timeToCompleteSeconds: respondent.timeToCompleteSeconds,
    answers: questions.map((q) => {
      const ans = answerMap.get(q.id);
      return {
        questionId: q.id,
        questionTitle: q.title,
        questionType: q.type,
        position: q.position,
        displayValue: formatAnswerDisplay(ans?.value ?? null),
      };
    }),
  };
}

/** Flat rows for CSV export. */
export async function getSurveyExportRows(
  surveyId: string,
  workspaceId: string,
  filters: AnalyticsFilters
): Promise<{ headers: string[]; rows: Record<string, string>[] } | null> {
  const survey = await getScopedSurvey(surveyId, workspaceId);
  if (!survey) return null;

  const { Respondent, Answer } = initDb();
  const questions = (survey.get("questions") as Question[] | undefined) ?? [];

  const respondents = await Respondent.findAll({
    where: buildRespondentWhere(surveyId, filters),
    order: [["startedAt", "ASC"]],
  });

  const respondentIds = respondents.map((r) => r.id);
  const allAnswers =
    respondentIds.length > 0
      ? await Answer.findAll({
          where: { respondentId: { [Op.in]: respondentIds } },
        })
      : [];

  const answerLookup = new Map<string, Map<string, Answer>>();
  for (const ans of allAnswers) {
    let byQ = answerLookup.get(ans.respondentId);
    if (!byQ) {
      byQ = new Map();
      answerLookup.set(ans.respondentId, byQ);
    }
    byQ.set(ans.questionId, ans);
  }

  const qHeaders = questions.map(
    (q, i) => `Q${i + 1}: ${q.title.replace(/"/g, '""')}`
  );
  const headers = [
    "respondent_id",
    "status",
    "started_at",
    "completed_at",
    "time_to_complete_seconds",
    ...qHeaders,
  ];

  const rows = respondents.map((r) => {
    const byQ = answerLookup.get(r.id);
    const row: Record<string, string> = {
      respondent_id: r.id,
      status: r.status,
      started_at: r.startedAt.toISOString(),
      completed_at: r.completedAt?.toISOString() ?? "",
      time_to_complete_seconds: String(r.timeToCompleteSeconds ?? ""),
    };

    questions.forEach((q, i) => {
      const ans = byQ?.get(q.id);
      row[qHeaders[i]] = formatAnswerDisplay(ans?.value ?? null);
    });

    return row;
  });

  return { headers, rows };
}
