import { settingsRepository } from '../db/repositories/settingsRepository.js'
import { exerciseRepository } from '../db/repositories/exerciseRepository.js'
import { comboRepository } from '../db/repositories/comboRepository.js'
import { sessionRepository } from '../db/repositories/sessionRepository.js'
import { collectionRepository } from '../db/repositories/collectionRepository.js'
import { executionLogRepository } from '../db/repositories/executionLogRepository.js'
import { buildEnvelope, downloadJson } from '../utils/jsonEnvelope.js'

const SETTINGS_EXPORT_KEYS = ['user_name', 'language', 'theme', 'backup_reminder_days']

function mergeById(existing = [], additional = []) {
  const map = new Map(existing.map((item) => [item.id, item]))
  for (const item of additional) {
    if (!map.has(item.id)) map.set(item.id, item)
  }
  return [...map.values()]
}

/** Exercises referenced by a combo's exercise list, fetched as full entities. */
async function collectComboDependencies(combos) {
  const exerciseIds = new Set()
  for (const combo of combos) {
    for (const ex of combo.exercises || []) {
      exerciseIds.add(ex.exercise_id)
    }
  }
  const exercises = (
    await Promise.all([...exerciseIds].map((id) => exerciseRepository.getById(id)))
  ).filter(Boolean)
  return { exercises }
}

/** Combos and exercises referenced by a session's items, including the combos' own exercises. */
async function collectSessionDependencies(sessions) {
  const exerciseIds = new Set()
  const comboIds = new Set()
  for (const session of sessions) {
    for (const item of session.items || []) {
      if (item.item_type === 'exercise') exerciseIds.add(item.item_id)
      if (item.item_type === 'combo') comboIds.add(item.item_id)
    }
  }
  const combos = (await Promise.all([...comboIds].map((id) => comboRepository.getById(id)))).filter(
    Boolean,
  )
  const { exercises: comboExercises } = await collectComboDependencies(combos)
  for (const exercise of comboExercises) {
    exerciseIds.add(exercise.id)
  }
  const exercises = (
    await Promise.all([...exerciseIds].map((id) => exerciseRepository.getById(id)))
  ).filter(Boolean)
  return { combos, exercises }
}

/** Sessions referenced by a collection, plus their transitive combo/exercise dependencies. */
async function collectCollectionDependencies(collections) {
  const sessionIds = new Set()
  for (const collection of collections) {
    for (const s of collection.sessions || []) {
      sessionIds.add(s.session_id)
    }
  }
  const sessions = (
    await Promise.all([...sessionIds].map((id) => sessionRepository.getById(id)))
  ).filter(Boolean)
  const { combos, exercises } = await collectSessionDependencies(sessions)
  return { sessions, combos, exercises }
}

/**
 * Composable for exporting all entity types as JSON.
 */
export function useJsonExport() {
  async function getExporter() {
    const name = await settingsRepository.get('user_name')
    const uuid = await settingsRepository.get('user_uuid')
    return { name: name || 'User', uuid }
  }

  async function exportAllExercises(query) {
    const exercises = query
      ? await exerciseRepository.search(query)
      : await exerciseRepository.getAll()
    const exporter = await getExporter()
    const envelope = buildEnvelope({ exercises }, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-exercises-${date}.json`, json)
  }

  async function exportExercise(exerciseId) {
    const exercise = await exerciseRepository.getById(exerciseId)
    if (!exercise) throw new Error('Exercise not found')
    const exporter = await getExporter()
    const envelope = buildEnvelope({ exercises: [exercise] }, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-exercises-${date}.json`, json)
  }

  async function exportAllCombos(query) {
    const combos = query
      ? await comboRepository.searchWithExercises(query)
      : await comboRepository.getAllWithExercises()
    const { exercises } = await collectComboDependencies(combos)
    const exporter = await getExporter()
    const envelope = buildEnvelope({ combos, exercises }, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-combos-${date}.json`, json)
  }

  async function exportCombo(comboId) {
    const combo = await comboRepository.getById(comboId)
    if (!combo) throw new Error('Combo not found')
    const { exercises } = await collectComboDependencies([combo])
    const exporter = await getExporter()
    const envelope = buildEnvelope({ combos: [combo], exercises }, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-combos-${date}.json`, json)
  }

  async function exportAllSessions(query) {
    const sessions = query
      ? await sessionRepository.searchWithItems(query)
      : await sessionRepository.getAllWithItems()
    const { combos, exercises } = await collectSessionDependencies(sessions)
    const exporter = await getExporter()
    const envelope = buildEnvelope({ sessions, combos, exercises }, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-sessions-${date}.json`, json)
  }

  async function exportSession(sessionId) {
    const session = await sessionRepository.getById(sessionId)
    if (!session) throw new Error('Session not found')
    const { combos, exercises } = await collectSessionDependencies([session])
    const exporter = await getExporter()
    const envelope = buildEnvelope({ sessions: [session], combos, exercises }, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-sessions-${date}.json`, json)
  }

  async function exportAllCollections(query) {
    const collections = query
      ? await collectionRepository.searchWithSessions(query)
      : await collectionRepository.getAllWithSessions()
    const { sessions, combos, exercises } = await collectCollectionDependencies(collections)
    const exporter = await getExporter()
    const envelope = buildEnvelope({ collections, sessions, combos, exercises }, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-collections-${date}.json`, json)
  }

  async function exportCollection(collectionId) {
    const collection = await collectionRepository.getById(collectionId)
    if (!collection) throw new Error('Collection not found')
    const { sessions, combos, exercises } = await collectCollectionDependencies([collection])
    const exporter = await getExporter()
    const envelope = buildEnvelope(
      { collections: [collection], sessions, combos, exercises },
      exporter,
    )
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-collections-${date}.json`, json)
  }

  async function exportAllExecutionLogs() {
    const executionLogs = await executionLogRepository.getAllWithItems()
    const exporter = await getExporter()
    const envelope = buildEnvelope({ executionLogs }, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-logs-${date}.json`, json)
  }

  async function exportSettings() {
    const allSettings = await settingsRepository.getAll()
    const data = {}
    for (const key of SETTINGS_EXPORT_KEYS) {
      if (allSettings[key] !== undefined) {
        data[key] = allSettings[key]
      }
    }
    const exporter = await getExporter()
    const envelope = buildEnvelope({ settings: data }, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-settings-${date}.json`, json)
  }

  async function exportAll() {
    const [exercises, combos, sessions, collections, executionLogs] = await Promise.all([
      exerciseRepository.getAll(),
      comboRepository.getAllWithExercises(),
      sessionRepository.getAllWithItems(),
      collectionRepository.getAllWithSessions(),
      executionLogRepository.getAllWithItems(),
    ])

    const allSettings = await settingsRepository.getAll()
    const settingsData = {}
    for (const key of SETTINGS_EXPORT_KEYS) {
      if (allSettings[key] !== undefined) {
        settingsData[key] = allSettings[key]
      }
    }

    const data = { exercises, combos, sessions, collections, executionLogs, settings: settingsData }
    const exporter = await getExporter()
    const envelope = buildEnvelope(data, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    await downloadJson(`trainus-full-${date}.json`, json)
  }

  async function exportByFilter(options = {}) {
    const data = {}
    const exporter = await getExporter()

    if (options.exercises) {
      data.exercises = await exerciseRepository.getAll()
    }
    if (options.combos) {
      data.combos = await comboRepository.getAllWithExercises()
    }
    if (options.sessions) {
      data.sessions = await sessionRepository.getAllWithItems()
    }
    if (options.collections) {
      data.collections = await collectionRepository.getAllWithSessions()
    }

    // Bundle dependencies so a partial selection (e.g. sessions without combos/exercises)
    // still produces a self-contained, importable export.
    if (data.collections) {
      const deps = await collectCollectionDependencies(data.collections)
      data.sessions = mergeById(data.sessions, deps.sessions)
      data.combos = mergeById(data.combos, deps.combos)
      data.exercises = mergeById(data.exercises, deps.exercises)
    }
    if (data.sessions) {
      const deps = await collectSessionDependencies(data.sessions)
      data.combos = mergeById(data.combos, deps.combos)
      data.exercises = mergeById(data.exercises, deps.exercises)
    }
    if (data.combos) {
      const deps = await collectComboDependencies(data.combos)
      data.exercises = mergeById(data.exercises, deps.exercises)
    }

    if (options.executionLogs) {
      data.executionLogs = await executionLogRepository.getAllWithItems()
    }
    if (options.settings) {
      const allSettings = await settingsRepository.getAll()
      const settingsData = {}
      for (const key of SETTINGS_EXPORT_KEYS) {
        if (allSettings[key] !== undefined) {
          settingsData[key] = allSettings[key]
        }
      }
      data.settings = settingsData
    }

    const envelope = buildEnvelope(data, exporter)
    const json = JSON.stringify(envelope, null, 2)
    const date = new Date().toISOString().slice(0, 10)
    const filename = `trainus-data-${date}.json`
    await downloadJson(filename, json)
  }

  return {
    exportAllExercises,
    exportExercise,
    exportAllCombos,
    exportCombo,
    exportAllSessions,
    exportSession,
    exportAllCollections,
    exportCollection,
    exportAllExecutionLogs,
    exportSettings,
    exportAll,
    exportByFilter,
  }
}
