/**
 * 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,
  BodyZone,
} from '../types/workoutProgram'
import type { TabataSession, TabataBlock, TabataExercise, TimedMovement } from '../types/program'

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

const CACHE_KEY = 'tabatafit-workout-programs-cache-v2'
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,
    },
    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,
    },
    rounds: row.rounds,
    workTime: row.work_time,
    restTime: row.rest_time,
  }
}

function rowsToWorkoutProgram(
  programRow: WorkoutProgramRow,
  tabataRows: WorkoutTabataRow[],
): WorkoutProgram {
  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,
    tabatas: tabataRows
      .sort((a, b) => a.position - b.position)
      .map(tabataRowToWorkoutTabata),
    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.
 * Uses cache first, falls back to Supabase.
 */
export async function fetchAllPrograms(): Promise<WorkoutProgram[]> {
  // Always fetch fresh from Supabase (cache can become stale after schema changes)
  if (!isSupabaseConfigured()) {
    const cached = await getCachedPrograms()
    return cached ?? []
  }
  if (!isSupabaseConfigured()) return []

  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) return []

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

  if (tabError || !tabataRows) return []

  // Group tabatas by program
  const tabatasByProgram = new Map<string, WorkoutTabataRow[]>()
  for (const t of tabataRows) {
    const existing = tabatasByProgram.get(t.program_id) ?? []
    existing.push(t)
    tabatasByProgram.set(t.program_id, existing)
  }

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

  await setCachedPrograms(programs)
  return programs
}

/**
 * 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 ─────────────────────

/** Generic warmup movements */
const GENERIC_WARMUP: TimedMovement[] = [
  { name: 'Jumping jacks', nameEn: 'Jumping jacks', duration: 30 },
  { name: 'Rotation des bras', nameEn: 'Arm circles', duration: 30 },
  { name: 'Montées de genoux', nameEn: 'High knees', duration: 30 },
  { name: 'Squats légers', nameEn: 'Bodyweight squats', duration: 30 },
]

/** Generic cooldown movements */
const GENERIC_COOLDOWN: TimedMovement[] = [
  { name: 'Étirement des quadriceps', nameEn: 'Quad stretch', duration: 30 },
  { name: 'Étirement des ischio-jambiers', nameEn: 'Hamstring stretch', duration: 30 },
  { name: 'Respiration profonde', nameEn: 'Deep breathing', duration: 30 },
]

/**
 * Convert a full WorkoutProgram (all 3 tabatas) to a TabataSession for the player.
 * All tabata blocks are played sequentially in one session.
 */
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,
    }
  })

  // Calculate total duration: warmup (2min) + all tabatas + inter-block rests (1min each) + cooldown (1.5min)
  const tabatasDuration = blocks.reduce(
    (sum, b) => sum + (b.rounds * (b.workTime + b.restTime)) / 60,
    0,
  )
  const interBlockRest = Math.max(0, blocks.length - 1) // 1 min between blocks
  const totalDuration = 2 + tabatasDuration + interBlockRest + 1.5

  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: GENERIC_WARMUP,
      totalDuration: 120,
    },
    blocks,
    cooldown: {
      movements: GENERIC_COOLDOWN,
      totalDuration: 90,
    },
    equipment: [],
    totalRounds,
    totalDuration: Math.ceil(totalDuration),
    calories: Math.ceil(totalRounds * calorieMultiplier),
    musicVibe: program.musicVibe,
  }
}
