import { eachDayOfInterval, format, parseISO, subMonths } from "date-fns";
import type { DateRange as DayPickerDateRange } from "react-day-picker";
import type { GraphDataset } from "../../types/medical";
import type { APIPrescription, APITest } from "../../services/medical";
import { METRIC_CONFIG, type APIDeviceReading, type MetricId } from "../../devices/types";
import { normalizeSearchText } from "../../utils/text.ts";

export type TimeframePreset = "all" | "1m" | "3m" | "6m" | "12m" | "custom";

export interface ParsedTestQuery {
  test_id: string | null;
  test_parameter: string;
  start?: string;
  end?: string;
  text: string;
}

export interface ParsedQueryView {
  testQueries: ParsedTestQuery[];
  rawQuery: string;
  outOfReferenceOnly: boolean;
}

export type DateRange = {
  startDate?: string;
  endDate?: string;
};

export const parseDateOption = (dateValue: string): Date => {
  const [year, month, day] = dateValue.split("-").map(Number);
  return new Date(year, (month || 1) - 1, day || 1);
};

export const toYmd = (dateValue: Date): string => format(dateValue, "yyyy-MM-dd");

export const rangesEqual = (
  left: DayPickerDateRange | undefined,
  right: DayPickerDateRange | undefined
): boolean => {
  const leftFrom = left?.from?.getTime() ?? null;
  const leftTo = left?.to?.getTime() ?? null;
  const rightFrom = right?.from?.getTime() ?? null;
  const rightTo = right?.to?.getTime() ?? null;

  return leftFrom === rightFrom && leftTo === rightTo;
};

export const getRangeForPreset = (
  dates: string[],
  preset: Exclude<TimeframePreset, "custom">
): DateRange => {
  if (dates.length === 0) return {};
  if (preset === "all") return {};

  const latestDate = parseISO(dates[dates.length - 1]);
  const months = preset === "1m" ? 1 : preset === "3m" ? 3 : preset === "6m" ? 6 : 12;
  const threshold = subMonths(latestDate, months);

  return {
    startDate: format(threshold, "yyyy-MM-dd"),
    endDate: dates[dates.length - 1],
  };
};

export const createDatasetInstanceId = (datasetId: string): string => {
  const suffix = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
  return `${datasetId}::${suffix}`;
};

const createSafeDatasetIdSuffix = (value: string): string => {
  const normalized = normalizeSearchText(value);
  const slug = normalized.replace(/\s+/g, "-") || "dataset";

  let hash = 0;
  for (let index = 0; index < value.length; index += 1) {
    hash = Math.imul(31, hash) + value.charCodeAt(index);
    hash |= 0;
  }

  return `${slug}-${(hash >>> 0).toString(36)}`;
};

export const formatParsedResponse = (data: unknown): ParsedQueryView => {
  const emptyState: ParsedQueryView = {
    testQueries: [],
    rawQuery: "",
    outOfReferenceOnly: false,
  };

  if (typeof data === "string") {
    return emptyState;
  }

  if (!data || typeof data !== "object") {
    return emptyState;
  }

  const record = data as Record<string, unknown>;
  const testQueries = Array.isArray(record.test_queries)
    ? record.test_queries
        .filter((item): item is Record<string, unknown> => !!item && typeof item === "object")
        .map((item) => ({
          test_id: typeof item.test_id === "string" ? item.test_id : null,
          test_parameter: String(item.test_parameter ?? ""),
          start: String(item.start ?? ""),
          end: String(item.end ?? ""),
          text: String(item.text ?? ""),
        }))
        .filter((item) => item.test_parameter || item.start || item.end || item.text)
    : [];

  return {
    testQueries,
    rawQuery: typeof record.raw_query === "string" ? record.raw_query : "",
    outOfReferenceOnly: Boolean(record.out_of_reference_only),
  };
};

export const formatParsedQueryRange = (query: ParsedTestQuery): string => {
  const start = query.start;
  const end = query.end;

  if (!start && !end) return "All time";
  if (start && end) return start === end ? start : `${start} → ${end}`;
  return `${start} - ${end}`;
};

export const shouldParseSearchQuery = (query: string): boolean => {
  const normalized = normalizeSearchText(query);
  return normalized.length >= 3;
};

export const datasetHasOutOfReferencePoint = (dataset: GraphDataset): boolean => {
  return dataset.data.some((point) => {
    if (typeof point.value !== "number") return false;

    const refMin = point.refMin ?? dataset.refMin;
    const refMax = point.refMax ?? dataset.refMax;
    if (typeof refMin !== "number" || typeof refMax !== "number") return false;

    return point.value < refMin || point.value > refMax;
  });
};

export const isDateWithinRange = (date: string, range: DateRange): boolean => {
  if (!range.startDate && !range.endDate) return true;

  const current = new Date(date).getTime();
  if (range.startDate && current < new Date(range.startDate).getTime()) return false;
  if (range.endDate && current > new Date(range.endDate).getTime()) return false;
  return true;
};

export const resolveDatasetsForTest = (
  availableDatasets: GraphDataset[],
  testId: string
): GraphDataset[] => {
  return availableDatasets.filter((dataset) => dataset.sourceTestIds?.includes(testId));
};

const parseValidDate = (value?: string | null): Date | null => {
  if (!value) return null;

  const parsed = parseISO(value);
  return Number.isNaN(parsed.getTime()) ? null : parsed;
};

const buildDailyDates = (startValue: string, endValue: string): string[] => {
  const start = parseValidDate(startValue);
  const end = parseValidDate(endValue);

  if (!start || !end) return [];

  const intervalStart = start <= end ? start : end;
  const intervalEnd = end >= start ? end : start;

  return eachDayOfInterval({ start: intervalStart, end: intervalEnd }).map((date) =>
    format(date, "yyyy-MM-dd")
  );
};

export const buildDatasetsFromTests = (tests: APITest[]): GraphDataset[] => {
  const subsetMap: Record<
    string,
    {
      unit: string;
      refMin?: number;
      refMax?: number;
      data: Array<{
        date: string;
        value: number;
        unit: string;
        refMin?: number;
        refMax?: number;
      }>;
      sourceTestIds: string[];
    }
  > = {};

  tests.forEach((test) => {
    const date = test.date.split("T")[0];
    (test.subtests || []).forEach((subtest) => {
      if (subtest.value === null || subtest.value === undefined) return;
      if (!subsetMap[subtest.name]) {
        subsetMap[subtest.name] = {
          unit: subtest.unit || "",
          data: [],
          refMin: subtest.refMin || undefined,
          refMax: subtest.refMax || undefined,
          sourceTestIds: [],
        };
      }

      subsetMap[subtest.name].data.push({
        date,
        value: subtest.value,
        unit: subtest.unit || "",
        refMin: subtest.refMin ?? undefined,
        refMax: subtest.refMax ?? undefined,
      });

      if (!subsetMap[subtest.name].sourceTestIds.includes(test._id)) {
        subsetMap[subtest.name].sourceTestIds.push(test._id);
      }

      if (subtest.refMin !== null && subtest.refMin !== undefined && !subsetMap[subtest.name].refMin) {
        subsetMap[subtest.name].refMin = subtest.refMin;
      }

      if (subtest.refMax !== null && subtest.refMax !== undefined && !subsetMap[subtest.name].refMax) {
        subsetMap[subtest.name].refMax = subtest.refMax;
      }
    });
  });

  const colors = ["#e11d48", "#dc2626", "#f59e0b", "#10b981", "#8b5cf6", "#64748b", "#be185d", "#3b82f6", "#38bdf8", "#eab308"];
  let colorIdx = 0;

  return Object.keys(subsetMap).map((name) => {
    const dataset = subsetMap[name];
    const color = colors[colorIdx % colors.length];
    colorIdx += 1;

    return {
      id: `test-${createSafeDatasetIdSuffix(name)}`,
      name,
      type: "line" as const,
      color,
      unit: dataset.unit,
      data: dataset.data.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()),
      sourceTestIds: dataset.sourceTestIds,
      refMin: dataset.refMin,
      refMax: dataset.refMax,
    };
  });
};

export const buildDatasetsFromDeviceReadings = (
  readingsByMetric: Partial<Record<MetricId, APIDeviceReading[]>>
): GraphDataset[] => {
  const colors = ["#0891b2", "#0284c7", "#2563eb", "#7c3aed", "#0d9488", "#16a34a", "#ca8a04", "#ea580c", "#e11d48", "#64748b"];
  let colorIdx = 0;

  return (Object.keys(METRIC_CONFIG) as MetricId[]).reduce<GraphDataset[]>((datasets, metric) => {
    const readings = (readingsByMetric[metric] || [])
      .filter((reading) => reading.date && typeof reading.value === "number")
      .sort((a, b) => a.date.localeCompare(b.date));

    if (readings.length === 0) return datasets;

    const config = METRIC_CONFIG[metric];
    const color = colors[colorIdx % colors.length];
    colorIdx += 1;
    const unit = readings.find((reading) => reading.unit)?.unit || config.unit;

    datasets.push({
      id: `wearable-${metric}`,
      name: config.label,
      type: "line",
      color,
      unit,
      data: readings.map((reading) => ({
        date: reading.date,
        value: reading.value,
        unit: reading.unit || unit,
      })),
    });

    return datasets;
  }, []);
};

export const buildDatasetsFromPrescriptions = (prescriptions: APIPrescription[]): GraphDataset[] => {
  const colors = ["#0f766e", "#14b8a6", "#059669", "#10b981", "#0ea5e9", "#2563eb", "#8b5cf6", "#c026d3"];
  let colorIdx = 0;

  return prescriptions
    .filter((prescription) => Boolean(prescription.medication))
    .flatMap((prescription) => {
      const displayName =
        prescription.medication?.brandName ||
        prescription.medication?.genericName ||
        prescription.prescriptionId ||
        "Medication";

      const startValue = prescription.startDate || prescription.prescribedDate || prescription.created_at || "";
      const endValue = prescription.endDate || prescription.updated_at || new Date().toISOString();
      const dateSeries = buildDailyDates(startValue, endValue);

      if (dateSeries.length === 0) return [];

      const color = colors[colorIdx % colors.length];
      colorIdx += 1;

      return [
        {
          id: `prescription-${prescription._id}`,
          name: displayName,
          type: "medication" as const,
          unit: "days",
          color,
          data: dateSeries.map((date) => ({
            date,
            value: 1,
            unit: "days",
          })),
        },
      ];
    });
};
