/**
 * Übungen: Liste/CRUD, Medien & Archiv-Anbindung, Progressionsgraphen, KI-Hilfen.
 */

import { stripHtmlToText } from '../utils/htmlUtils'
import { normalizeAdvanceMode, parseComboRepSeriesCountUi } from '../utils/combinationMethodProfileUi'
import { request, API_URL, ACTIVE_CLUB_STORAGE_KEY } from './client.js'

/** Wie `mergeActiveClubHeader` in client.js — lokal, damit Raw-`fetch`-Pfade nicht von einem Namensimport abhängen. */
function withActiveClubHeaders(headers = {}) {
  const cid = localStorage.getItem(ACTIVE_CLUB_STORAGE_KEY)
  if (cid && /^\d+$/.test(String(cid).trim())) {
    return { ...headers, 'X-Active-Club-Id': String(cid).trim() }
  }
  return { ...headers }
}

// ============================================================================
// Exercises
// ============================================================================

export async function listExercises(filters = {}) {
  const q = new URLSearchParams()
  Object.entries(filters).forEach(([k, v]) => {
    if (v === undefined || v === null) return
    if (typeof v === 'boolean') {
      q.set(k, v ? 'true' : 'false')
      return
    }
    if (Array.isArray(v)) {
      if (v.length === 0) return
      v.forEach((item) => {
        if (item !== '' && item !== undefined && item !== null && String(item).trim() !== '') {
          q.append(k, String(item))
        }
      })
      return
    }
    if (String(v).trim() !== '') q.set(k, String(v))
  })
  const query = q.toString()
  return request(`/api/exercises${query ? '?' + query : ''}`)
}

/** Formular → API-Body (M:N gemäß EXERCISES_API_SPEC + training_types) */
export function buildExerciseApiPayload(formData, extras = {}) {
  const num = (v) => (v === '' || v == null ? null : Number(v))

  const goalHtml = formData.goal || ''
  const execHtml = formData.execution || ''
  const goalText = stripHtmlToText(goalHtml)
  const execText = stripHtmlToText(execHtml)

  if (!goalText && !execText) {
    throw new Error('Ziel oder Durchführung ausfüllen (mindestens eines, auch mit Formatierung).')
  }

  const mapFocus = (formData.focus_areas_multi || [])
    .filter((x) => x && x.focus_area_id)
    .map((x) => ({ focus_area_id: Number(x.focus_area_id), is_primary: !!x.is_primary }))
  const mapStyles = (formData.training_styles_multi || [])
    .filter((x) => x && x.training_style_id)
    .map((x) => ({ training_style_id: Number(x.training_style_id), is_primary: !!x.is_primary }))
  const mapTTypes = (formData.training_types_multi || [])
    .filter((x) => x && x.training_type_id)
    .map((x) => ({ training_type_id: Number(x.training_type_id), is_primary: !!x.is_primary }))
  const mapTg = (formData.target_groups_multi || [])
    .filter((x) => x && x.target_group_id)
    .map((x) => ({ target_group_id: Number(x.target_group_id), is_primary: !!x.is_primary }))

  const visibilityNorm = String(formData.visibility || 'private').trim().toLowerCase()

  const payload = {
    title: (formData.title || '').trim(),
    summary: formData.summary || null,
    goal: goalHtml.trim() ? goalHtml : null,
    execution: execHtml.trim() ? execHtml : null,
    preparation: formData.preparation || null,
    trainer_notes: formData.trainer_notes || null,
    duration_min: num(formData.duration_min),
    duration_max: num(formData.duration_max),
    group_size_min: num(formData.group_size_min),
    group_size_max: num(formData.group_size_max),
    equipment: Array.isArray(formData.equipment) ? formData.equipment : [],
    focus_areas_multi: mapFocus,
    training_styles_multi: mapStyles,
    training_types_multi: mapTTypes,
    target_groups_multi: mapTg,
    age_groups: [],
    skills: (formData.skills || []).map((s) => ({
      skill_id: s.skill_id,
      is_primary: !!s.is_primary,
      intensity: s.intensity || null,
      required_level: s.required_level || null,
      target_level: s.target_level || null,
    })),
    visibility: visibilityNorm,
    status: formData.status || 'draft',
    club_id: visibilityNorm === 'club' ? num(formData.club_id) : null,
    exercise_kind:
      String(formData.exercise_kind || 'simple').toLowerCase() === 'combination'
        ? 'combination'
        : 'simple',
    ...extras,
  }

  const isCombo = payload.exercise_kind === 'combination'

  if (isCombo) {
    let mpObj = {}
    const mpRaw = typeof formData.method_profile_json === 'string' ? formData.method_profile_json.trim() : ''
    if (mpRaw) {
      try {
        const parsed = JSON.parse(mpRaw)
        if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
          throw new Error('Ablaufprofil muss ein JSON-Objekt sein.')
        }
        mpObj = parsed
      } catch (e) {
        if (e instanceof SyntaxError) {
          throw new Error('Ablaufprofil (JSON): Syntax ungültig.')
        }
        throw e
      }
    }

    const slotRows = Array.isArray(formData.combination_slots) ? formData.combination_slots : []
    const combination_slots = []

    function parseTimingField(raw) {
      if (raw === '' || raw == null || raw === undefined) return undefined
      const n = parseInt(String(raw), 10)
      return Number.isFinite(n) ? n : undefined
    }

    for (let i = 0; i < slotRows.length; i += 1) {
      const row = slotRows[i] || {}
      let ids = Array.isArray(row.candidate_exercise_ids)
        ? row.candidate_exercise_ids.map((x) => Number(x)).filter((n) => Number.isFinite(n))
        : []

      /** Legacy: noch idsText Unterstützung für Import von älteren FormStand */
      if ((!ids || ids.length === 0) && typeof row.idsText === 'string' && row.idsText.trim()) {
        ids = row.idsText
          .split(/[\s,;]+/)
          .map((s) => s.trim())
          .filter(Boolean)
          .map((s) => parseInt(s, 10))
          .filter((n) => Number.isFinite(n))
      }

      combination_slots.push({
        slot_index: i,
        title: (typeof row.title === 'string' && row.title.trim()) || null,
        candidate_exercise_ids: ids,
      })
    }

    const slot_profiles_v1_next = []
    for (let i = 0; i < slotRows.length; i += 1) {
      const row = slotRows[i] || {}
      const o = { slot_index: i }
      const advanceMode = normalizeAdvanceMode(row.advance_mode)
      if (advanceMode !== 'timed') o.advance_mode = advanceMode
      const load = parseTimingField(row.load_sec)
      const crs = parseTimingField(row.consecutive_reps)
      const rsc = parseTimingField(row.rep_series_count)
      const intra = parseTimingField(row.intra_rep_rest_sec)
      const tran = parseTimingField(row.transition_after_sec)
      const serienUi = parseComboRepSeriesCountUi(row.rep_series_count)
      const allowInterSeriesPause =
        advanceMode === 'timed' ||
        ((advanceMode === 'rep' || advanceMode === 'manual') && serienUi >= 2)

      if (advanceMode === 'timed' && load !== undefined && load >= 0) o.load_sec = Math.round(load)
      if (crs !== undefined && crs >= 1) o.consecutive_reps = Math.round(crs)
      if (
        rsc !== undefined &&
        rsc >= 1 &&
        (advanceMode === 'rep' || advanceMode === 'manual')
      ) {
        o.rep_series_count = Math.round(rsc)
      }
      if (intra !== undefined && intra >= 0 && allowInterSeriesPause) o.intra_rep_rest_sec = Math.round(intra)
      if (tran !== undefined && tran >= 0) o.transition_after_sec = Math.round(tran)
      if (Object.keys(o).length > 1) slot_profiles_v1_next.push(o)
    }

    payload.method_archetype = (formData.method_archetype || '').trim() || null
    if (slot_profiles_v1_next.length > 0) mpObj.slot_profiles_v1 = slot_profiles_v1_next
    else delete mpObj.slot_profiles_v1
    payload.method_profile = mpObj
    payload.combination_slots = combination_slots
  } else {
    payload.method_archetype = null
    payload.method_profile = {}
  }

  return payload
}

export async function uploadExerciseMedia(exerciseId, formData) {
  const token = localStorage.getItem('authToken')
  const headers = withActiveClubHeaders({})
  if (token) headers['X-Auth-Token'] = token
  const response = await fetch(`${API_URL}/api/exercises/${exerciseId}/media`, {
    method: 'POST',
    headers,
    body: formData,
  })
  if (!response.ok) {
    const text = await response.text()
    let parsed = null
    try {
      parsed = text ? JSON.parse(text) : null
    } catch {
      parsed = null
    }
    const d = parsed?.detail
    if (
      response.status === 409 &&
      d &&
      typeof d === 'object' &&
      !Array.isArray(d) &&
      typeof d.code === 'string'
    ) {
      const e = new Error(
        typeof d.message === 'string' ? d.message : 'Upload konnte nicht verarbeitet werden',
      )
      e.code = d.code
      e.status = 409
      e.payload = d
      throw e
    }
    if (response.status === 413) {
      const nginx = (text || '').toLowerCase().includes('nginx')
      throw new Error(
        nginx
          ? 'Die Anfrage ist zu groß (413). Häufig: nginx „client_max_body_size“ — z. B. große/r mehrere Videos oder Bulk-Upload. Dateien kleiner aufteilen oder Server-Limit erhöhen (Frontend-Image Neu bauen).'
          : 'Die Anfrage ist zu groß (413). Dateigröße oder Server-Limit prüfen.',
      )
    }
    const msg =
      typeof d === 'string'
        ? d
        : d != null && typeof d === 'object' && typeof d.message === 'string'
          ? d.message
          : d != null
            ? JSON.stringify(d)
            : text && text.length < 400 && !/^\s*</.test(text)
              ? `HTTP ${response.status}: ${text.trim()}`
              : `HTTP ${response.status}`
    throw new Error(msg)
  }
  return response.json()
}

export async function updateExerciseMedia(exerciseId, mediaId, data) {
  return request(`/api/exercises/${exerciseId}/media/${mediaId}`, {
    method: 'PUT',
    body: JSON.stringify(data),
  })
}

export async function deleteExerciseMedia(exerciseId, mediaId) {
  return request(`/api/exercises/${exerciseId}/media/${mediaId}`, { method: 'DELETE' })
}

export async function reorderExerciseMedia(exerciseId, mediaIds) {
  return request(`/api/exercises/${exerciseId}/media/reorder`, {
    method: 'PUT',
    body: JSON.stringify({ media_ids: mediaIds }),
  })
}

/** Papierkorb / Recovery — `action`: trash_soft | trash_hidden | recover | purge | reactivate */
export async function postMediaAssetLifecycle(assetId, action, extra = {}) {
  return request(`/api/media-assets/${assetId}/lifecycle`, {
    method: 'POST',
    body: JSON.stringify({ action, ...extra }),
  })
}

/** Archiv: aktive media_assets sichtbar für den Nutzer (Bibliotheksrechte). */
export async function listMediaAssets(params = {}) {
  const sp = new URLSearchParams()
  if (params.q) sp.set('q', params.q)
  if (params.limit != null) sp.set('limit', String(params.limit))
  if (params.offset != null) sp.set('offset', String(params.offset))
  if (params.lifecycle) sp.set('lifecycle', String(params.lifecycle))
  if (params.media_kind) sp.set('media_kind', String(params.media_kind))
  if (params.club_id != null && params.club_id !== '') sp.set('club_id', String(params.club_id))
  if (params.uploaded_by != null && params.uploaded_by !== '') sp.set('uploaded_by', String(params.uploaded_by))
  if (params.include_filter_meta) sp.set('include_filter_meta', 'true')
  const qs = sp.toString()
  return request(`/api/media-assets${qs ? `?${qs}` : ''}`)
}

export async function patchMediaAsset(assetId, data) {
  return request(`/api/media-assets/${assetId}`, {
    method: 'PATCH',
    body: JSON.stringify(data),
  })
}

export async function bulkMediaLifecycle(data) {
  return request('/api/media-assets/bulk-lifecycle', {
    method: 'POST',
    body: JSON.stringify(data),
  })
}

export async function bulkPatchMediaAssets(data) {
  return request('/api/media-assets/bulk-patch', {
    method: 'POST',
    body: JSON.stringify(data),
  })
}

/**
 * Mehrere Dateien ins Medienarchiv (`POST /api/media-assets/bulk-upload`).
 * @param {File[]} files
 * @param {{ visibility?: string, club_id?: number }} [options]
 */
export async function bulkUploadMediaAssets(files, options = {}) {
  const visibility = options.visibility || 'private'
  const token = localStorage.getItem('authToken')
  const headers = withActiveClubHeaders({})
  if (token) headers['X-Auth-Token'] = token
  const formData = new FormData()
  formData.append('visibility', String(visibility))
  if (options.club_id != null && options.club_id !== '') {
    formData.append('club_id', String(options.club_id))
  }
  // Copyright + P-06: Rechte-Erklaerung + Kontextfelder
  if (options.copyright_notice != null && String(options.copyright_notice).trim())
    formData.append('copyright_notice', String(options.copyright_notice).trim())
  const p06Fields = [
    'rights_holder_confirmed',
    'contains_identifiable_persons',
    'person_consent_confirmed',
    'person_consent_context',
    'contains_minors',
    'parental_consent_confirmed',
    'parental_consent_context',
    'contains_music',
    'music_rights_confirmed',
    'music_rights_context',
    'contains_third_party_content',
    'third_party_rights_confirmed',
    'third_party_rights_context',
  ]
  for (const f of p06Fields) {
    if (options[f] != null && options[f] !== '') formData.append(f, String(options[f]))
  }
  const arr = Array.isArray(files) ? files : [files]
  for (const f of arr) {
    if (f) formData.append('files', f)
  }
  const url = `${API_URL}/api/media-assets/bulk-upload`
  const response = await fetch(url, {
    method: 'POST',
    headers,
    body: formData,
  })
  if (!response.ok) {
    const text = await response.text()
    let parsed = null
    try {
      parsed = JSON.parse(text)
    } catch {
      parsed = null
    }
    if (parsed?.detail != null) {
      const d = parsed.detail
      throw new Error(typeof d === 'string' ? d : JSON.stringify(d))
    }
    const snippet = (text || '').replace(/\s+/g, ' ').trim().slice(0, 180)
    throw new Error(snippet ? `HTTP ${response.status}: ${snippet}` : `HTTP ${response.status}`)
  }
  return response.json()
}

/** Übung: bestehendes Archiv-Medium verknüpfen (`POST /api/exercises/{id}/media/from-asset`). */
export async function getMediaAssetJournal(assetId) {
  return request(`/api/admin/media-rights/assets/${assetId}/journal`)
}

export async function addMediaAssetDeclarationCorrection(assetId, body) {
  return request(`/api/admin/media-rights/assets/${assetId}/correction`, {
    method: 'POST',
    body: JSON.stringify(body),
  })
}

export async function attachExerciseMediaFromAsset(exerciseId, body) {
  return request(`/api/exercises/${exerciseId}/media/from-asset`, {
    method: 'POST',
    body: JSON.stringify(body),
  })
}

// P-11: Legal-Hold-Endpunkte
export async function setMediaAssetLegalHold(assetId, reasonCode, reasonNote) {
  return request(`/api/admin/media-assets/${assetId}/legal-hold`, {
    method: 'POST',
    body: JSON.stringify({ reason_code: reasonCode, reason_note: reasonNote }),
  })
}

export async function releaseMediaAssetLegalHold(assetId, releaseNote) {
  return request(`/api/admin/media-assets/${assetId}/legal-hold/release`, {
    method: 'POST',
    body: JSON.stringify({ release_note: releaseNote }),
  })
}

export async function listMediaAssetsWithLegalHold(limit = 100, offset = 0) {
  return request(`/api/admin/media-assets/legal-hold?limit=${limit}&offset=${offset}`)
}

export async function getExercise(id) {
  return request(`/api/exercises/${id}`)
}

export async function createExercise(data) {
  return request('/api/exercises', {
    method: 'POST',
    body: JSON.stringify(data),
  })
}

export async function updateExercise(id, data) {
  const token = localStorage.getItem('authToken')
  const headers = withActiveClubHeaders({ 'Content-Type': 'application/json' })
  if (token) headers['X-Auth-Token'] = token
  const url = `${API_URL}/api/exercises/${id}`
  const response = await fetch(url, {
    method: 'PUT',
    headers,
    body: JSON.stringify(data),
  })
  if (!response.ok) {
    const text = await response.text()
    let parsed = null
    try {
      parsed = JSON.parse(text)
    } catch {
      parsed = null
    }
    const d = parsed?.detail
    if (
      response.status === 422 &&
      d &&
      typeof d === 'object' &&
      !Array.isArray(d) &&
      typeof d.code === 'string'
    ) {
      const e = new Error(typeof d.message === 'string' ? d.message : 'Validierung fehlgeschlagen')
      e.status = 422
      e.code = d.code
      e.payload = d
      throw e
    }
    if (parsed?.detail != null) {
      const msg = typeof parsed.detail === 'string' ? parsed.detail : JSON.stringify(parsed.detail)
      throw new Error(msg)
    }
    const snippet = (text || '').replace(/\s+/g, ' ').trim().slice(0, 180)
    throw new Error(snippet ? `HTTP ${response.status}: ${snippet}` : `HTTP ${response.status}`)
  }
  return response.json()
}

/** Massenänderung Übungen: Sichtbarkeit, Status, Katalog-Zuordnungen (`PATCH /api/exercises/bulk-metadata`). */
export async function bulkPatchExercisesMetadata(data) {
  return request('/api/exercises/bulk-metadata', {
    method: 'PATCH',
    body: JSON.stringify(data),
  })
}

export async function deleteExercise(id) {
  return request(`/api/exercises/${id}`, { method: 'DELETE' })
}

export async function createExerciseVariant(exerciseId, data) {
  return request(`/api/exercises/${exerciseId}/variants`, {
    method: 'POST',
    body: JSON.stringify(data),
  })
}

export async function updateExerciseVariant(exerciseId, variantId, data) {
  return request(`/api/exercises/${exerciseId}/variants/${variantId}`, {
    method: 'PUT',
    body: JSON.stringify(data),
  })
}

export async function deleteExerciseVariant(exerciseId, variantId) {
  return request(`/api/exercises/${exerciseId}/variants/${variantId}`, { method: 'DELETE' })
}

export async function reorderExerciseVariants(exerciseId, variantIds) {
  return request(`/api/exercises/${exerciseId}/variants/reorder`, {
    method: 'PUT',
    body: JSON.stringify({ variant_ids: variantIds }),
  })
}

// Progressionsgraphen (Übung → Übung), Migration 032/033
export async function listExerciseProgressionGraphs() {
  return request('/api/exercise-progression-graphs')
}

export async function getExerciseProgressionGraph(id, { includeEdges = false } = {}) {
  const q = includeEdges ? '?include_edges=true' : ''
  return request(`/api/exercise-progression-graphs/${id}${q}`)
}

export async function createExerciseProgressionGraph(data) {
  return request('/api/exercise-progression-graphs', {
    method: 'POST',
    body: JSON.stringify(data),
  })
}

export async function updateExerciseProgressionGraph(id, data) {
  return request(`/api/exercise-progression-graphs/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data),
  })
}

export async function deleteExerciseProgressionGraph(id) {
  return request(`/api/exercise-progression-graphs/${id}`, { method: 'DELETE' })
}

export async function listExerciseProgressionEdges(graphId, query = {}) {
  const q = new URLSearchParams()
  if (query.from_exercise_id != null) q.set('from_exercise_id', String(query.from_exercise_id))
  if (query.to_exercise_id != null) q.set('to_exercise_id', String(query.to_exercise_id))
  const qs = q.toString()
  return request(`/api/exercise-progression-graphs/${graphId}/edges${qs ? `?${qs}` : ''}`)
}

export async function createExerciseProgressionEdge(graphId, data) {
  return request(`/api/exercise-progression-graphs/${graphId}/edges`, {
    method: 'POST',
    body: JSON.stringify(data),
  })
}

export async function updateExerciseProgressionEdge(graphId, edgeId, data) {
  return request(`/api/exercise-progression-graphs/${graphId}/edges/${edgeId}`, {
    method: 'PUT',
    body: JSON.stringify(data),
  })
}

export async function deleteExerciseProgressionEdge(graphId, edgeId) {
  return request(`/api/exercise-progression-graphs/${graphId}/edges/${edgeId}`, { method: 'DELETE' })
}

export async function createExerciseProgressionSequence(graphId, data) {
  return request(`/api/exercise-progression-graphs/${graphId}/edges/sequence`, {
    method: 'POST',
    body: JSON.stringify(data),
  })
}

export async function deleteExerciseProgressionEdgesBatch(graphId, edgeIds) {
  return request(`/api/exercise-progression-graphs/${graphId}/edges/delete-batch`, {
    method: 'POST',
    body: JSON.stringify({ edge_ids: edgeIds }),
  })
}

/** KI-Ausbaustufe (EXERCISES_API_SPEC): benötigt Backend + z. B. OPENROUTER_API_KEY. */
export async function suggestExerciseAi(payload) {
  return request('/api/exercises/ai/suggest', {
    method: 'POST',
    body: JSON.stringify(payload),
  })
}

export async function regenerateExerciseAi(exerciseId, payload) {
  return request(`/api/exercises/${exerciseId}/ai/regenerate`, {
    method: 'POST',
    body: JSON.stringify(payload),
  })
}
