import {
  ChallengeMode,
  ChallengeProgressStatus,
  GoalDirection,
  GoalMetric,
} from "src/common/enums/challenges.enum";
import {
  calculateHabitProgress,
  resolveHabitCadence,
} from "./habitWindow.helper";

const MS_PER_DAY = 24 * 60 * 60 * 1000;

/** Days left before the challenge window closes. null = open-ended. */
export function remainingDays(
  startedAt: Date | string,
  duration: number,
  now: Date = new Date()
): number | null {
  if (!duration || duration <= 0) return null;
  const end = new Date(startedAt).getTime() + duration * MS_PER_DAY;
  return Math.max(Math.ceil((end - now.getTime()) / MS_PER_DAY), 0);
}

/**
 * Best value logged for a metric across all attempts — the "personal record".
 * For LTE goals (time trials) the best attempt is the SMALLEST one.
 * Returns null when nothing has been logged yet, which must stay distinct from
 * 0: a 0 would read as "already beat the 12min target" on an LTE goal.
 */
function bestValue(
  progress: any[],
  field: string,
  direction: GoalDirection | string = GoalDirection.GTE
): number | null {
  const values = (progress || [])
    .map((p) => p?.[field])
    .filter((v) => typeof v === "number" && Number.isFinite(v));
  if (!values.length) return null;
  return direction === GoalDirection.LTE
    ? Math.min(...values)
    : Math.max(...values);
}

/** The progress field a goal metric is measured on. */
export function fieldForMetric(
  metric: GoalMetric | string
): "reps" | "weight" | "duration" {
  switch (metric) {
    case GoalMetric.REPS:
    case GoalMetric.REPS_DELTA:
      return "reps";
    case GoalMetric.WEIGHT:
    case GoalMetric.WEIGHT_DELTA:
    case GoalMetric.WEIGHT_PCT:
      return "weight";
    case GoalMetric.DURATION:
    case GoalMetric.DURATION_DELTA:
      return "duration";
    default:
      return "reps";
  }
}

const pct = (done: number, total: number) =>
  total > 0 ? Math.max(Math.min(Math.round((done / total) * 100), 100), 0) : 0;

/**
 * PERFORMANCE — an absolute benchmark. Progress is the user's personal record
 * (the best single attempt), measured against goal.value.
 *
 * NOTE: assumes "higher is better". Challenges where a LOWER value wins (e.g.
 * "Run 3km in 12min") need a direction flag on the goal to score correctly —
 * still open, see the discussion on Typ 2.
 */
export function performanceProgress(challenge: any, membership: any) {
  const goal = challenge?.goal;
  const metric = goal?.metric;
  const target = Number(goal?.value) || 0;
  const direction = goal?.direction ?? GoalDirection.GTE;
  const isLte = direction === GoalDirection.LTE;
  const field = fieldForMetric(metric);
  const best = bestValue(membership?.progress, field, direction);
  const attempts = (membership?.progress || []).length;

  // nothing logged yet -> no record, nothing "achieved"
  if (best === null) {
    return {
      metric: metric ?? null,
      direction,
      target,
      current: null,
      remaining: target,
      percent: 0,
      attempts,
      is_goal_met: false,
    };
  }

  // GTE: climb up to the target.  LTE: come down to it.
  const remaining = isLte
    ? Math.max(best - target, 0)
    : Math.max(target - best, 0);
  const percent = isLte
    ? best > 0
      ? Math.min(Math.round((target / best) * 100), 100)
      : 100
    : pct(best, target);

  return {
    metric: metric ?? null,
    direction,
    target,
    // "personal record" shown on the detail screen
    current: best,
    remaining,
    percent,
    attempts,
    is_goal_met: target > 0 && (isLte ? best <= target : best >= target),
  };
}

/**
 * VOLUME — relative to the user's own starting value. The goal is a delta
 * (+4 reps, +5kg, +30sec) or a percentage (+10% weight), so the absolute target
 * only exists once the baseline has been entered.
 */
export function volumeProgress(challenge: any, membership: any) {
  const goal = challenge?.goal;
  const metric = goal?.metric;
  const goalValue = Number(goal?.value) || 0;
  const field = fieldForMetric(metric);
  const baseline = membership?.baseline;
  const baseValue = Number(baseline?.[field]);
  const hasBaseline = Number.isFinite(baseValue);

  const best = bestValue(membership?.progress, field);
  const current = best ?? (hasBaseline ? baseValue : 0);

  // absolute value the user must reach
  let target: number | null = null;
  if (hasBaseline) {
    target =
      metric === GoalMetric.WEIGHT_PCT
        ? baseValue * (1 + goalValue / 100)
        : baseValue + goalValue;
  }

  // progress expressed in the goal's own unit (% for weight_pct, else absolute)
  let gained = 0;
  if (hasBaseline) {
    gained =
      metric === GoalMetric.WEIGHT_PCT
        ? baseValue > 0 ? ((current - baseValue) / baseValue) * 100 : 0
        : current - baseValue;
  }
  const gainedRounded = Math.round(gained * 10) / 10;
  const remaining = Math.max(Math.round((goalValue - gained) * 10) / 10, 0);

  return {
    metric: metric ?? null,
    // starting value the user entered via set-baseline
    baseline: hasBaseline ? baseValue : null,
    has_baseline: hasBaseline,
    // the goal in its own unit: +20 (%) or +5 (kg) or +4 (reps)
    goal_value: goalValue,
    is_percentage: metric === GoalMetric.WEIGHT_PCT,
    target,
    current,
    // e.g. gained 12 (%) of a 20% goal -> 8 still to go
    gained: gainedRounded,
    remaining,
    percent: pct(gained, goalValue),
    is_goal_met: hasBaseline && goalValue > 0 && gained >= goalValue,
  };
}

/**
 * VOLUME levels as the detail screen shows them — "Starting level 80 kg x 5
 * reps", "Goal 80 kg x 9 reps", "Current best 80 kg x 5 reps".
 * Only the goal's own metric moves; the other side of the pair stays at the
 * baseline (a "+4 reps" goal is 4 more reps AT THE SAME WEIGHT).
 */
export function volumeLevels(challenge: any, membership: any) {
  const metric = challenge?.goal?.metric;
  const field = fieldForMetric(metric);
  const summary = volumeProgress(challenge, membership);
  const baseline = membership?.baseline || {};

  const pair = (value: number | null) => ({
    weight: field === "weight" ? value : baseline?.weight ?? null,
    reps: field === "reps" ? value : baseline?.reps ?? null,
    duration: field === "duration" ? value : null,
  });

  return {
    starting: pair(summary.baseline),
    goal: pair(summary.target === null ? null : Math.round(summary.target * 10) / 10),
    current: pair(summary.current ?? summary.baseline),
  };
}

/**
 * Describes the input(s) the UI must render to log against a challenge, so the
 * form is driven by the challenge instead of hard-coded per screen.
 *   HABIT       -> nothing to enter, just the "Done today" toggle
 *   PERFORMANCE -> one value for the goal metric + a date ("Became stronger?")
 *   VOLUME      -> same attempt input, plus a "starting value" (baseline) form
 * Field `key` matches exactly what mark-day / set-baseline expect in the body.
 */
export interface ChallengeInputField {
  key: "reps" | "weight" | "duration";
  label: string;
  type: "number" | "time"; // time = min:sec picker (holds/runs), else a number
  unit: string; // "kg" | "sec" | ""
  min: number;
}

const FIELD_META: Record<
  ChallengeInputField["key"],
  { en: string; de: string; type: "number" | "time"; unit: string }
> = {
  reps: { en: "Repetitions", de: "Wiederholungen", type: "number", unit: "" },
  weight: { en: "Weight", de: "Gewicht", type: "number", unit: "kg" },
  duration: { en: "Time", de: "Zeit", type: "time", unit: "sec" },
};

function inputField(
  key: ChallengeInputField["key"],
  lang: string
): ChallengeInputField {
  const m = FIELD_META[key];
  return {
    key,
    label: lang === "de" ? m.de : m.en,
    type: m.type,
    unit: m.unit,
    min: 0,
  };
}

export function buildChallengeForm(challenge: any, lang = "en") {
  const mode = challenge?.type;
  const field = fieldForMetric(challenge?.goal?.metric); // reps | weight | duration

  // HABIT: just a completion toggle, nothing to type.
  if (mode === ChallengeMode.HABIT) {
    return {
      attempt: { fields: [] as ChallengeInputField[], show_date: false },
      baseline: null,
    };
  }

  // PERFORMANCE / VOLUME: log one value for the goal metric, with a date.
  const attempt = { fields: [inputField(field, lang)], show_date: true };

  if (mode === ChallengeMode.VOLUME) {
    // starting value: a hold time for time goals, else weight x reps.
    const baselineFields =
      field === "duration"
        ? [inputField("duration", lang)]
        : [inputField("weight", lang), inputField("reps", lang)];
    return { attempt, baseline: { fields: baselineFields } };
  }

  return { attempt, baseline: null };
}

/**
 * Live status for a PERFORMANCE / VOLUME membership.
 * Unlike habit there are no windows — the only ways to leave ACTIVE are hitting
 * the goal, or running out of time on a challenge that has a duration.
 * Open-ended challenges (duration 0) can never fail; they stay active until met.
 */
export function resolveAttemptStatus(
  challenge: any,
  membership: any,
  summary: { is_goal_met: boolean },
  now: Date = new Date()
): ChallengeProgressStatus {
  if (summary?.is_goal_met) return ChallengeProgressStatus.COMPLETED;

  const duration = Number(challenge?.duration) || 0;
  if (duration > 0) {
    const startedAt = membership?.started_at ?? membership?.createdAt;
    const deadline = new Date(startedAt).getTime() + duration * MS_PER_DAY;
    if (now.getTime() > deadline) return ChallengeProgressStatus.FAILED;
  }
  return ChallengeProgressStatus.ACTIVE;
}

/**
 * One challenge widget for the progress dashboard. Shape differs per mode:
 *   HABIT       -> completions across windows (+ the dots)
 *   PERFORMANCE -> personal record vs an absolute target
 *   VOLUME      -> gain vs the user's own starting value
 */
export function buildChallengeWidget(
  challenge: any,
  membership: any,
  lang = "en",
  now: Date = new Date()
) {
  const startedAt = membership?.started_at ?? membership?.createdAt;
  const mode = challenge?.type;

  const base = {
    challenge_id: challenge?._id,
    membership_id: membership?._id,
    type: mode ?? null,
    title: challenge?.title?.[lang] ?? null,
    short_desc: challenge?.short_desc?.[lang] ?? null,
    attachment: challenge?.attachment ?? null,
    duration: challenge?.duration ?? 0,
    started_at: startedAt ?? null,
    // the "7 days" / "25 days" badge; null when the challenge is open-ended
    remaining_days: remainingDays(startedAt, challenge?.duration, now),
    status: membership?.status ?? ChallengeProgressStatus.ACTIVE,
  };

  if (mode === ChallengeMode.HABIT) {
    const state = calculateHabitProgress(
      resolveHabitCadence(challenge),
      startedAt,
      membership?.progress,
      now
    );
    return {
      ...base,
      // habit status is time-derived, so the live value wins over the stored one
      status: state.status,
      // HABIT: calendar days left — duration minus elapsed days, with today
      // counted off once logged (7-day challenge => 6 after today's session).
      // Not window/session based. Overrides the plain countdown from `base`:
      // remaining_days: remainingDays(startedAt, challenge?.duration, now),
      remaining_days: state.remaining_days,
      progress: {
        metric: GoalMetric.SESSIONS,
        target: state.target,
        current: state.completed_count,
        remaining: state.remaining,
        // percent: pct(state.completed_count, state.target),
        // is_goal_met: state.status === ChallengeProgressStatus.COMPLETED,
        // the dots: `windows` = one per period, `slots` = one per session
        // (so 2x-a-week renders two dots per week). Same shape as the detail API.
        windows: state.windows,
        slots: state.slots,
        // total_windows: state.total_windows,
       current_window: state.current_window,
        // current_window_remaining: state.current_window_remaining,
        // current_window_ends_at: state.current_window_ends_at,
        // missed_windows: state.missed_windows,
        // can_mark_now:
        //   state.status === ChallengeProgressStatus.ACTIVE &&
        //   state.current_window_remaining > 0,
      },
    };
  }

  const summary =
    mode === ChallengeMode.VOLUME
      ? volumeProgress(challenge, membership)
      : performanceProgress(challenge, membership);

  return {
    ...base,
    // goal-met / deadline-passed win over the stored value
    status: resolveAttemptStatus(challenge, membership, summary, now),
    progress: summary,
  };
}
