/**
 * Workout Programs Data Access Layer
 * Fetches body-zone workout programs from Supabase with offline caching
 */

import { supabase, isSupabaseConfigured } from '../supabase/client'
import AsyncStorage from '@react-native-async-storage/async-storage'
import type {
  WorkoutProgram,
  WorkoutTabata,
  WorkoutProgramRow,
  WorkoutTabataRow,
  WorkoutTimedExerciseRow,
  WarmupBlock,
  StretchBlock,
  TimedExercise,
  BodyZone,
} from '../types/workoutProgram'
import type { TabataSession, TabataBlock, TabataExercise, TimedMovement } from '../types/program'

// ─── Constants ──────────────────────────────────────────────────

const CACHE_KEY = 'tabatafit-workout-programs-cache-v3'
const CACHE_TTL = 1000 * 60 * 60 // 1 hour

// ─── Row Mappers ────────────────────────────────────────────────

function tabataRowToWorkoutTabata(row: WorkoutTabataRow): WorkoutTabata {
  return {
    id: row.id,
    position: row.position,
    exercise1: {
      name: row.exercise_1_name,
      nameEn: row.exercise_1_name_en ?? '',
      tip: row.exercise_1_tip ?? undefined,
      tipEn: row.exercise_1_tip_en ?? undefined,
      modification: row.exercise_1_modification ?? undefined,
      modificationEn: row.exercise_1_modification_en ?? undefined,
      progression: row.exercise_1_progression ?? undefined,
      progressionEn: row.exercise_1_progression_en ?? undefined,
      videoUrl: row.exercise_1_video_url ?? null,
    },
    exercise2: {
      name: row.exercise_2_name,
      nameEn: row.exercise_2_name_en ?? '',
      tip: row.exercise_2_tip ?? undefined,
      tipEn: row.exercise_2_tip_en ?? undefined,
      modification: row.exercise_2_modification ?? undefined,
      modificationEn: row.exercise_2_modification_en ?? undefined,
      progression: row.exercise_2_progression ?? undefined,
      progressionEn: row.exercise_2_progression_en ?? undefined,
      videoUrl: row.exercise_2_video_url ?? null,
    },
    rounds: row.rounds,
    workTime: row.work_time,
    restTime: row.rest_time,
  }
}

function timedExerciseRowToTimedExercise(row: WorkoutTimedExerciseRow): TimedExercise {
  return {
    name: row.name,
    nameEn: row.name_en ?? '',
    duration: row.duration,
    videoUrl: row.video_url ?? null,
    tip: row.tip ?? undefined,
    tipEn: row.tip_en ?? undefined,
  }
}

function buildWarmupBlock(rows: WorkoutTimedExerciseRow[]): WarmupBlock {
  const exercises = rows
    .sort((a, b) => a.position - b.position)
    .map(timedExerciseRowToTimedExercise)
  const totalDuration = exercises.reduce((sum, e) => sum + e.duration, 0)
  return { exercises, totalDuration }
}

function buildStretchBlock(rows: WorkoutTimedExerciseRow[]): StretchBlock {
  const exercises = rows
    .sort((a, b) => a.position - b.position)
    .map(timedExerciseRowToTimedExercise)
  const totalDuration = exercises.reduce((sum, e) => sum + e.duration, 0)
  return { exercises, totalDuration }
}

/** Placeholder blocks until admin-web seeds real content */
const DEFAULT_WARMUP: WarmupBlock = {
  exercises: [
    { name: 'Jumping jacks', nameEn: 'Jumping jacks', duration: 45, videoUrl: null },
    { name: 'Rotation des bras', nameEn: 'Arm circles', duration: 30, videoUrl: null },
    { name: 'Montées de genoux', nameEn: 'High knees', duration: 45, videoUrl: null },
    { name: 'Squats légers', nameEn: 'Light squats', duration: 30, videoUrl: null },
  ],
  totalDuration: 150,
}

const DEFAULT_STRETCH: StretchBlock = {
  exercises: [
    { name: 'Étirement quadriceps', nameEn: 'Quad stretch', duration: 30, videoUrl: null },
    { name: 'Étirement ischio-jambiers', nameEn: 'Hamstring stretch', duration: 30, videoUrl: null },
    { name: 'Étirement épaules', nameEn: 'Shoulder stretch', duration: 30, videoUrl: null },
    { name: 'Respiration profonde', nameEn: 'Deep breathing', duration: 30, videoUrl: null },
  ],
  totalDuration: 120,
}

function rowsToWorkoutProgram(
  programRow: WorkoutProgramRow,
  tabataRows: WorkoutTabataRow[],
  warmupRows: WorkoutTimedExerciseRow[],
  stretchRows: WorkoutTimedExerciseRow[],
): WorkoutProgram {
  const warmup = warmupRows.length > 0 ? buildWarmupBlock(warmupRows) : DEFAULT_WARMUP
  const stretch = stretchRows.length > 0 ? buildStretchBlock(stretchRows) : DEFAULT_STRETCH

  return {
    id: programRow.id,
    title: programRow.title,
    description: programRow.description,
    bodyZone: programRow.body_zone,
    level: programRow.level,
    isFree: programRow.is_free,
    musicVibe: programRow.music_vibe ?? 'electronic',
    estimatedDuration: programRow.estimated_duration,
    estimatedCalories: programRow.estimated_calories,
    icon: programRow.icon,
    accentColor: programRow.accent_color,
    sortOrder: programRow.sort_order,
    warmup,
    tabatas: tabataRows
      .sort((a, b) => a.position - b.position)
      .map(tabataRowToWorkoutTabata),
    stretch,
    createdAt: programRow.created_at,
    updatedAt: programRow.updated_at,
  }
}

// ─── Cache ──────────────────────────────────────────────────────

interface CacheEntry {
  programs: WorkoutProgram[]
  timestamp: number
}

async function getCachedPrograms(): Promise<WorkoutProgram[] | null> {
  try {
    const raw = await AsyncStorage.getItem(CACHE_KEY)
    if (!raw) return null
    const entry: CacheEntry = JSON.parse(raw)
    if (Date.now() - entry.timestamp > CACHE_TTL) return null
    return entry.programs
  } catch {
    return null
  }
}

async function setCachedPrograms(programs: WorkoutProgram[]): Promise<void> {
  try {
    const entry: CacheEntry = { programs, timestamp: Date.now() }
    await AsyncStorage.setItem(CACHE_KEY, JSON.stringify(entry))
  } catch {
    // Cache write failure is non-critical
  }
}

// ─── Fetch Functions ────────────────────────────────────────────

/**
 * Fetch all workout programs with their tabatas, warmup, and stretch.
 * Always fetches fresh from Supabase; falls back to cache if offline.
 */
export async function fetchAllPrograms(): Promise<WorkoutProgram[]> {
  if (!isSupabaseConfigured()) {
    const cached = await getCachedPrograms()
    return cached ?? []
  }

  const { data: programRows, error: progError } = await supabase
    .from('workout_programs')
    .select('*')
    .order('sort_order')
    .order('body_zone')
    .order('level')
    .returns<WorkoutProgramRow[]>()

  if (progError || !programRows?.length) {
    const cached = await getCachedPrograms()
    return cached ?? []
  }

  const { data: tabataRows } = await supabase
    .from('program_tabatas')
    .select('*')
    .order('position')
    .returns<WorkoutTabataRow[]>()

  const { data: warmupRows } = await supabase
    .from('workout_warmup_exercises')
    .select('*')
    .order('position')
    .returns<WorkoutTimedExerciseRow[]>()

  const { data: stretchRows } = await supabase
    .from('workout_stretch_exercises')
    .select('*')
    .order('position')
    .returns<WorkoutTimedExerciseRow[]>()

  const tabatasByProgram = groupBy(tabataRows ?? [], r => r.program_id)
  const warmupByProgram = groupBy(warmupRows ?? [], r => r.program_id)
  const stretchByProgram = groupBy(stretchRows ?? [], r => r.program_id)

  const programs = programRows.map(pr =>
    rowsToWorkoutProgram(
      pr,
      tabatasByProgram.get(pr.id) ?? [],
      warmupByProgram.get(pr.id) ?? [],
      stretchByProgram.get(pr.id) ?? [],
    ),
  )

  await setCachedPrograms(programs)
  return programs
}

function groupBy<T, K>(items: T[], keyFn: (t: T) => K): Map<K, T[]> {
  const map = new Map<K, T[]>()
  for (const item of items) {
    const key = keyFn(item)
    const bucket = map.get(key) ?? []
    bucket.push(item)
    map.set(key, bucket)
  }
  return map
}

/** Fetch programs filtered by body zone */
export async function fetchProgramsByBodyZone(
  bodyZone: BodyZone,
): Promise<WorkoutProgram[]> {
  const all = await fetchAllPrograms()
  return all.filter(p => p.bodyZone === bodyZone)
}

/** Fetch a single program by ID */
export async function fetchProgramById(id: string): Promise<WorkoutProgram | null> {
  const all = await fetchAllPrograms()
  return all.find(p => p.id === id) ?? null
}

/** Check if an ID refers to a workout program */
export function isWorkoutProgramId(id: string): boolean {
  return id.startsWith('wp-')
}

/** Parse a workout program ID like 'wp-{programId}' */
export function parseWorkoutProgramId(id: string): { programId: string } | null {
  if (!id.startsWith('wp-')) return null
  return { programId: id.slice(3) }
}

/** Build an ID for a workout program */
export function buildWorkoutProgramId(programId: string): string {
  return `wp-${programId}`
}

// ─── Adapter: WorkoutProgram → TabataSession ─────────────────────

function timedExerciseToMovement(e: TimedExercise): TimedMovement {
  return { name: e.name, nameEn: e.nameEn, duration: e.duration }
}

/**
 * Convert a full WorkoutProgram (warmup + 3 tabatas + stretch) to a TabataSession
 * for the legacy player. Uses program's warmup and stretch blocks directly.
 */
export function workoutProgramToTabataSession(
  program: WorkoutProgram,
): TabataSession {
  const blocks: TabataBlock[] = program.tabatas.map((tabata) => {
    const oddExercise: TabataExercise = {
      name: tabata.exercise1.name,
      nameEn: tabata.exercise1.nameEn,
      conseil: tabata.exercise1.tip ?? '',
      conseilEn: tabata.exercise1.tipEn ?? '',
      modification: tabata.exercise1.modification,
      modificationEn: tabata.exercise1.modificationEn,
      progression: tabata.exercise1.progression,
      progressionEn: tabata.exercise1.progressionEn,
    }
    const evenExercise: TabataExercise = {
      name: tabata.exercise2.name,
      nameEn: tabata.exercise2.nameEn,
      conseil: tabata.exercise2.tip ?? '',
      conseilEn: tabata.exercise2.tipEn ?? '',
      modification: tabata.exercise2.modification,
      modificationEn: tabata.exercise2.modificationEn,
      progression: tabata.exercise2.progression,
      progressionEn: tabata.exercise2.progressionEn,
    }
    return {
      id: tabata.id,
      oddExercise,
      evenExercise,
      rounds: tabata.rounds,
      workTime: tabata.workTime,
      restTime: tabata.restTime,
    }
  })

  const tabatasDurationSec = blocks.reduce(
    (sum, b) => sum + b.rounds * (b.workTime + b.restTime),
    0,
  )
  const interBlockRestSec = Math.max(0, blocks.length - 1) * 15 // 15s between blocks
  const totalSec = program.warmup.totalDuration + tabatasDurationSec + interBlockRestSec + program.stretch.totalDuration
  const totalDuration = Math.ceil(totalSec / 60)

  const totalRounds = blocks.reduce((sum, b) => sum + b.rounds, 0)
  const calorieMultiplier = program.level === 'Advanced' ? 12 : program.level === 'Intermediate' ? 9 : 7

  return {
    id: buildWorkoutProgramId(program.id),
    week: 1,
    order: 1,
    title: program.title,
    titleEn: program.title,
    description: program.description ?? '',
    descriptionEn: program.description ?? '',
    focus: [program.bodyZone],
    focusEn: [program.bodyZone],
    warmup: {
      movements: program.warmup.exercises.map(timedExerciseToMovement),
      totalDuration: program.warmup.totalDuration,
    },
    blocks,
    cooldown: {
      movements: program.stretch.exercises.map(timedExerciseToMovement),
      totalDuration: program.stretch.totalDuration,
    },
    equipment: [],
    totalRounds,
    totalDuration,
    calories: Math.ceil(totalRounds * calorieMultiplier),
    musicVibe: program.musicVibe,
  }
}
