import { ref } from 'vue'
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 { suffixRepository } from '../db/repositories/suffixRepository.js'
import { parseEnvelope, pickJsonFile } from '../utils/jsonEnvelope.js'

export const COLLISION_ACTION = {
  REPLACE: 'replace',
  SKIP: 'skip',
}

const SETTINGS_SKIP_KEYS = ['user_uuid', 'app_version', 'db_created_at', 'last_backup_at']

/**
 * Composable for importing data from JSON files.
 */
export function useJsonImport() {
  const importing = ref(false)
  const error = ref(null)

  async function loadFile() {
    const text = await pickJsonFile()
    if (!text) return { envelope: null, error: null, cancelled: true }

    const result = parseEnvelope(text)
    return { envelope: result.envelope, error: result.error, cancelled: false }
  }

  async function ensureSuffix(uuid, name, suffix) {
    const existing = await suffixRepository.getByUuid(uuid)
    if (existing) return existing.suffix

    await suffixRepository.create({ user_uuid: uuid, user_name: name, suffix })
    return suffix
  }

  // --- Full multi-entity import ---

  async function analyzeAll(envelope, { asMine = false } = {}) {
    const localUuid = await settingsRepository.get('user_uuid')
    const exporterUuid = envelope.metadata.exportedBy.uuid
    const exporterName = envelope.metadata.exportedBy.name
    const exportDate = envelope.metadata.exportDate
    const isSameUser = asMine || exporterUuid === localUuid

    const result = {
      summary: {
        exporterName,
        exporterUuid,
        exportDate,
        isSameUser,
        asMine,
        hasExercises: !!envelope.data.exercises?.length,
        hasCombos: !!envelope.data.combos?.length,
        hasSessions: !!envelope.data.sessions?.length,
        hasCollections: !!envelope.data.collections?.length,
        hasExecutionLogs: !!envelope.data.executionLogs?.length,
        hasSettings: !!envelope.data.settings,
      },
      exercises: null,
      combos: null,
      sessions: null,
      collections: null,
      executionLogs: null,
      settings: null,
    }

    // Analyze exercises
    if (envelope.data.exercises?.length) {
      result.exercises = await _analyzeEntityList(
        envelope.data.exercises,
        exerciseRepository,
        'title',
        exporterUuid,
        isSameUser,
      )
    }

    // Analyze combos
    if (envelope.data.combos?.length) {
      result.combos = await _analyzeEntityList(
        envelope.data.combos,
        comboRepository,
        'title',
        exporterUuid,
        isSameUser,
      )
    }

    // Analyze sessions
    if (envelope.data.sessions?.length) {
      result.sessions = await _analyzeEntityList(
        envelope.data.sessions,
        sessionRepository,
        'title',
        exporterUuid,
        isSameUser,
      )
    }

    // Analyze collections
    if (envelope.data.collections?.length) {
      result.collections = await _analyzeEntityList(
        envelope.data.collections,
        collectionRepository,
        'label',
        exporterUuid,
        isSameUser,
      )
    }

    // Analyze execution logs (no title collision, just ID existence)
    if (envelope.data.executionLogs?.length) {
      result.executionLogs = await _analyzeEntityList(
        envelope.data.executionLogs,
        executionLogRepository,
        null,
        exporterUuid,
        isSameUser,
      )
    }

    // Settings
    if (envelope.data.settings) {
      result.settings = envelope.data.settings
    }

    // Suffix handling for different-user imports
    if (!isSameUser) {
      const suffixEntry = await suffixRepository.getByUuid(exporterUuid)
      result.suffixInfo = {
        needsSuffix: !suffixEntry,
        existingSuffix: suffixEntry?.suffix || null,
      }
    }

    return result
  }

  async function _analyzeEntityList(entities, repository, titleField, exporterUuid, isSameUser) {
    const result = {
      total: entities.length,
      noCollision: [],
      sameUserCollisions: [],
      differentUser: [],
    }

    // Same-user title conflicts (different id, same title) are detected once up front
    // (O(n) instead of O(n²)) and folded into sameUserCollisions alongside id conflicts.
    let titleMap = null
    if (isSameUser && titleField) {
      const allRows = await repository.getAll()
      titleMap = new Map(allRows.map((row) => [row[titleField], row]))
    }

    for (const entity of entities) {
      const existing = await repository.getById(entity.id)

      if (isSameUser) {
        if (existing) {
          result.sameUserCollisions.push({ imported: entity, existing })
        } else {
          const titleMatch = titleMap?.get(entity[titleField])
          if (titleMatch) {
            result.sameUserCollisions.push({ imported: entity, existing: titleMatch })
          } else {
            result.noCollision.push(entity)
          }
        }
      } else {
        if (existing && existing.created_by === exporterUuid) {
          result.differentUser.push({ imported: entity, existing, action: 'update' })
        } else if (existing) {
          result.differentUser.push({ imported: entity, existing, action: 'new_id' })
        } else {
          result.differentUser.push({ imported: entity, existing: null, action: 'insert' })
        }
      }
    }

    return result
  }

  async function applyAllImport(analysis, options = {}) {
    const {
      suffix,
      exerciseResolutions = [],
      comboResolutions = [],
      sessionResolutions = [],
      collectionResolutions = [],
    } = options

    const localUuid = await settingsRepository.get('user_uuid')
    const exporterUuid = analysis.summary.exporterUuid
    const exporterName = analysis.summary.exporterName
    const isSameUser = analysis.summary.isSameUser

    const idMap = { exercise: {}, combo: {}, session: {}, collection: {}, executionLog: {} }
    const report = {
      exercises: { imported: 0, skipped: 0, replaced: 0, updated: 0 },
      combos: { imported: 0, skipped: 0, replaced: 0, updated: 0 },
      sessions: { imported: 0, skipped: 0, replaced: 0, updated: 0 },
      collections: { imported: 0, skipped: 0, replaced: 0, updated: 0 },
      executionLogs: { imported: 0, skipped: 0 },
      settings: { imported: 0 },
      errors: [],
    }

    // Register suffix if needed
    if (!isSameUser && analysis.suffixInfo?.needsSuffix && suffix) {
      await ensureSuffix(exporterUuid, exporterName, suffix)
    }

    const effectiveSuffix = suffix || analysis.suffixInfo?.existingSuffix

    // 1. Import exercises
    if (analysis.exercises) {
      await _importEntityList(
        analysis.exercises,
        exerciseRepository,
        'title',
        isSameUser,
        localUuid,
        exporterUuid,
        effectiveSuffix,
        exerciseResolutions,
        idMap.exercise,
        report.exercises,
        report.errors,
      )
    }

    // 2. Import combos (remap exercise_id references)
    if (analysis.combos) {
      await _importEntityList(
        analysis.combos,
        comboRepository,
        'title',
        isSameUser,
        localUuid,
        exporterUuid,
        effectiveSuffix,
        comboResolutions,
        idMap.combo,
        report.combos,
        report.errors,
        { remapExercises: idMap.exercise },
        async (comboId, combo) => {
          if (combo.exercises?.length) {
            const exercises = combo.exercises.map((ex) => ({
              exerciseId: ex.exercise_id,
              defaultReps: ex.default_reps ?? 1,
              defaultWeight: ex.default_weight ?? 0,
            }))
            await comboRepository.setExercises(comboId, exercises)
          }
        },
      )
    }

    // 3. Import sessions (remap exercise_id and combo_id references)
    if (analysis.sessions) {
      await _importEntityList(
        analysis.sessions,
        sessionRepository,
        'title',
        isSameUser,
        localUuid,
        exporterUuid,
        effectiveSuffix,
        sessionResolutions,
        idMap.session,
        report.sessions,
        report.errors,
        { remapExercises: idMap.exercise, remapCombos: idMap.combo },
        async (sessionId, session) => {
          if (session.items?.length) {
            const items = session.items.map((item) => ({
              item_id: item.item_id,
              item_type: item.item_type,
              repetitions: item.repetitions ?? 1,
              weight: item.weight ?? 0,
            }))
            await sessionRepository.setItems(sessionId, items)
          }
        },
      )
    }

    // 4. Import collections (remap session_id references)
    if (analysis.collections) {
      await _importEntityList(
        analysis.collections,
        collectionRepository,
        'label',
        isSameUser,
        localUuid,
        exporterUuid,
        effectiveSuffix,
        collectionResolutions,
        idMap.collection,
        report.collections,
        report.errors,
        { remapSessions: idMap.session },
        async (collectionId, collection) => {
          if (collection.sessions?.length) {
            const sessionIds = collection.sessions.map((s) => s.session_id)
            await collectionRepository.setSessions(collectionId, sessionIds)
          }
        },
      )
    }

    // 5. Import execution logs (remap session_id and exercise_id)
    if (analysis.executionLogs) {
      await _importExecutionLogs(
        analysis.executionLogs,
        isSameUser,
        localUuid,
        exporterUuid,
        idMap,
        report.executionLogs,
        report.errors,
      )
    }

    // 6. Import settings — only for same-user restores; skip entirely for cross-user imports
    if (analysis.settings && isSameUser) {
      for (const [key, value] of Object.entries(analysis.settings)) {
        if (!SETTINGS_SKIP_KEYS.includes(key)) {
          await settingsRepository.set(key, value)
          report.settings.imported++
        }
      }
    } else if (analysis.settings && !isSameUser) {
      report.settings = { skipped: true }
    }

    return report
  }

  async function _importEntityList(
    analysis,
    repository,
    titleField,
    isSameUser,
    localUuid,
    exporterUuid,
    suffix,
    resolutions,
    idMap,
    report,
    errors,
    remapOptions = {},
    setNestedData = null,
  ) {
    const resolutionMap = {}
    for (const r of resolutions) {
      resolutionMap[r.imported.id] = r
    }

    // Import no-collision entities (same-user path)
    for (const entity of analysis.noCollision) {
      try {
        const finalEntity = _prepareEntity(
          entity,
          titleField,
          isSameUser ? localUuid : exporterUuid,
          suffix,
          remapOptions,
        )
        await repository.createWithId(entity.id, finalEntity)
        idMap[entity.id] = entity.id
        report.imported++
        if (setNestedData) {
          try {
            await setNestedData(entity.id, finalEntity)
          } catch (ne) {
            errors.push(ne.message)
          }
        }
      } catch (e) {
        errors.push(e.message)
      }
    }

    // Handle same-user collisions (id match or title match — both resolve against the
    // existing row, so idMap correctly redirects same-batch references either way)
    for (const collision of analysis.sameUserCollisions) {
      const resolution = resolutionMap[collision.imported.id]
      if (!resolution || resolution.action === COLLISION_ACTION.SKIP) {
        report.skipped++
        idMap[collision.imported.id] = collision.existing.id
        continue
      }

      if (resolution.action === COLLISION_ACTION.REPLACE) {
        try {
          const finalEntity = _prepareEntity(
            collision.imported,
            titleField,
            localUuid,
            null,
            remapOptions,
          )
          await repository.replaceAll(collision.existing.id, finalEntity)
          idMap[collision.imported.id] = collision.existing.id
          report.replaced++
          if (setNestedData) {
            try {
              await setNestedData(collision.existing.id, finalEntity)
            } catch (ne) {
              errors.push(ne.message)
            }
          }
        } catch (e) {
          errors.push(e.message)
        }
      }
    }

    // Handle different-user entities. A resolution here means a title conflict was found
    // (on the suffixed title) against an existing row — redirect to it instead of the
    // default id-based action.
    for (const entry of analysis.differentUser) {
      const resolution = resolutionMap[entry.imported.id]
      try {
        if (resolution) {
          if (resolution.action === COLLISION_ACTION.SKIP) {
            report.skipped++
            idMap[entry.imported.id] = resolution.existing.id
            continue
          }

          const finalEntity = _prepareEntity(
            entry.imported,
            titleField,
            exporterUuid,
            suffix,
            remapOptions,
          )
          await repository.replaceAll(resolution.existing.id, finalEntity)
          idMap[entry.imported.id] = resolution.existing.id
          report.replaced++
          if (setNestedData) {
            try {
              await setNestedData(resolution.existing.id, finalEntity)
            } catch (ne) {
              errors.push(ne.message)
            }
          }
          continue
        }

        const entityId = entry.action === 'new_id' ? crypto.randomUUID() : entry.imported.id
        const finalEntity = _prepareEntity(
          entry.imported,
          titleField,
          exporterUuid,
          suffix,
          remapOptions,
        )

        if (entry.action === 'update') {
          await repository.replaceAll(entityId, finalEntity)
          idMap[entry.imported.id] = entityId
          report.updated++
        } else {
          await repository.createWithId(entityId, finalEntity)
          idMap[entry.imported.id] = entityId
          report.imported++
        }

        if (setNestedData) {
          try {
            await setNestedData(entityId, finalEntity)
          } catch (ne) {
            errors.push(ne.message)
          }
        }
      } catch (e) {
        errors.push(e.message)
      }
    }
  }

  function _prepareEntity(entity, titleField, createdBy, suffix, remapOptions) {
    const cloned = { ...entity }
    cloned.created_by = createdBy

    // Apply title/suffix
    if (titleField) {
      cloned[titleField] = suffix ? `${entity[titleField]} [${suffix}]` : entity[titleField]
    }

    // Remap combo exercises
    if (remapOptions.remapExercises && cloned.exercises?.length) {
      cloned.exercises = cloned.exercises.map((ex) => ({
        ...ex,
        exercise_id: remapOptions.remapExercises[ex.exercise_id] || ex.exercise_id,
      }))
    }

    // Remap session items
    if ((remapOptions.remapExercises || remapOptions.remapCombos) && cloned.items?.length) {
      cloned.items = cloned.items.map((item) => {
        if (item.item_type === 'exercise' && remapOptions.remapExercises) {
          return { ...item, item_id: remapOptions.remapExercises[item.item_id] || item.item_id }
        }
        if (item.item_type === 'combo' && remapOptions.remapCombos) {
          return { ...item, item_id: remapOptions.remapCombos[item.item_id] || item.item_id }
        }
        return item
      })
    }

    // Remap collection sessions
    if (remapOptions.remapSessions && cloned.sessions?.length) {
      cloned.sessions = cloned.sessions.map((s) => ({
        ...s,
        session_id: remapOptions.remapSessions[s.session_id] || s.session_id,
      }))
    }

    return cloned
  }

  async function _importExecutionLogs(
    analysis,
    isSameUser,
    localUuid,
    exporterUuid,
    idMap,
    report,
    errors,
  ) {
    // Same-user collisions mean the log already exists locally. Execution logs
    // are immutable historical records, so re-importing them is always a skip
    // (re-inserting would only produce PRIMARY KEY violations).
    report.skipped += (analysis.sameUserCollisions || []).length

    const logsToImport = [
      ...(analysis.noCollision || []).map((log) => ({ log, action: 'insert' })),
      ...(analysis.differentUser || []).map((e) => ({ log: e.imported, action: e.action })),
    ]

    for (const { log, action } of logsToImport) {
      try {
        const remappedSessionId = idMap.session[log.session_id] || log.session_id
        const sessionExists = await sessionRepository.getById(remappedSessionId)
        if (!sessionExists) {
          report.skipped++
          continue
        }

        const logId = action === 'new_id' ? crypto.randomUUID() : log.id
        await executionLogRepository.createWithId(logId, {
          ...log,
          session_id: remappedSessionId,
        })

        if (log.items?.length) {
          const remappedItems = log.items.map((item) => ({
            ...item,
            ...(action === 'new_id' ? { id: crypto.randomUUID(), log_id: logId } : {}),
            exercise_id: idMap.exercise[item.exercise_id] || item.exercise_id,
          }))
          await executionLogRepository.addItems(logId, remappedItems)
        }

        report.imported++
      } catch (e) {
        errors.push(e.message)
      }
    }
  }

  return {
    importing,
    error,
    loadFile,
    ensureSuffix,
    analyzeAll,
    applyAllImport,
  }
}
