import { getCheckInTasks } from '@/data/check-in'
import {
  CHECK_IN_BADGES,
  CHECK_IN_POINTS,
  type CheckInBadge,
  type CheckInProfile,
  type CheckInRecord,
  cloneCheckInProfile,
  createCheckInRecordId,
  createEmptyCheckInProfile,
  normalizeCheckInProfile,
} from '@/domain/check-in'

export const CHECK_IN_STORAGE_KEY = 'guangming:check-in-profile'
const CHECK_IN_STORAGE_SCHEMA_VERSION = 2
const LEGACY_CHECK_IN_STORAGE_SCHEMA_VERSION = 1
const envelopeKeys = new Set(['schemaVersion', 'savedAt', 'payload'])

interface CheckInStorageEnvelope {
  schemaVersion: typeof CHECK_IN_STORAGE_SCHEMA_VERSION
  savedAt: string
  payload: CheckInProfile
}

export class CheckInStorageError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'CheckInStorageError'
  }
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

function readLegacyTimestamp(value: unknown): string | null {
  if (typeof value !== 'string' || !Number.isFinite(Date.parse(value)))
    return null
  return new Date(value).toISOString()
}

function migrateLegacyProfile(value: unknown): CheckInProfile | null {
  if (!isRecord(value) || !Array.isArray(value.checkIns))
    return null

  const enabledPoiIds = new Set(getCheckInTasks().map(task => task.poiId))
  const uniquePoiIds = new Set<string>()
  const checkIns: CheckInRecord[] = []
  for (const entry of value.checkIns) {
    if (!isRecord(entry))
      return null
    const poiId = typeof entry.poiId === 'string' ? entry.poiId.trim() : ''
    const poiName = typeof entry.poiName === 'string' ? entry.poiName.trim() : ''
    const checkedInAt = readLegacyTimestamp(entry.checkedInAt)
    if (!poiId || !poiName || !checkedInAt)
      return null
    if (!enabledPoiIds.has(poiId) || uniquePoiIds.has(poiId))
      continue
    uniquePoiIds.add(poiId)
    checkIns.push({
      id: createCheckInRecordId(poiId, checkedInAt),
      poiId,
      poiName,
      checkedInAt,
      pointsAwarded: CHECK_IN_POINTS,
    })
  }

  checkIns.sort((left, right) => right.checkedInAt.localeCompare(left.checkedInAt))
  const badges: CheckInBadge[] = CHECK_IN_BADGES
    .filter(definition => checkIns.length >= definition.requiredCheckIns)
    .map((definition) => {
      const thresholdRecord = checkIns[checkIns.length - definition.requiredCheckIns]
      return { code: definition.code, obtainedAt: thresholdRecord.checkedInAt }
    })
  return normalizeCheckInProfile({
    points: checkIns.length * CHECK_IN_POINTS,
    badges,
    checkIns,
  })
}

function readValidatedProfile(value: Record<string, unknown>): { profile: CheckInProfile, migrated: boolean } | null {
  if (value.schemaVersion === CHECK_IN_STORAGE_SCHEMA_VERSION) {
    const profile = normalizeCheckInProfile(value.payload)
    return profile ? { profile, migrated: false } : null
  }
  if (value.schemaVersion === LEGACY_CHECK_IN_STORAGE_SCHEMA_VERSION) {
    const profile = migrateLegacyProfile(value.payload)
    return profile ? { profile, migrated: true } : null
  }
  return null
}

export function loadCheckInProfile(): CheckInProfile {
  let value: unknown
  try {
    value = uni.getStorageSync(CHECK_IN_STORAGE_KEY)
  }
  catch (error) {
    const reason = error instanceof Error && error.message ? `：${error.message}` : ''
    throw new CheckInStorageError(`无法读取本机打卡记录${reason}`)
  }

  if (value === undefined || value === null || value === '')
    return createEmptyCheckInProfile()

  if (!isRecord(value)
    || Object.keys(value).some(key => !envelopeKeys.has(key))
    || typeof value.savedAt !== 'string'
    || !Number.isFinite(Date.parse(value.savedAt))) {
    throw new CheckInStorageError('本机打卡记录异常，请先在“我的打卡”中清除后重试')
  }
  const result = readValidatedProfile(value)
  if (!result)
    throw new CheckInStorageError('本机打卡记录异常，请先在“我的打卡”中清除后重试')
  if (result.migrated)
    saveCheckInProfile(result.profile)
  return result.profile
}

export function saveCheckInProfile(profile: CheckInProfile): void {
  const normalized = normalizeCheckInProfile(profile)
  if (!normalized)
    throw new Error('打卡记录格式无效，未写入本机')

  const envelope: CheckInStorageEnvelope = {
    schemaVersion: CHECK_IN_STORAGE_SCHEMA_VERSION,
    savedAt: new Date().toISOString(),
    payload: cloneCheckInProfile(normalized),
  }
  try {
    uni.setStorageSync(CHECK_IN_STORAGE_KEY, envelope)
  }
  catch (error) {
    const reason = error instanceof Error && error.message ? `：${error.message}` : ''
    throw new Error(`无法保存本机打卡记录${reason}`)
  }
}

export function clearCheckInProfile(): void {
  try {
    uni.removeStorageSync(CHECK_IN_STORAGE_KEY)
  }
  catch (error) {
    const reason = error instanceof Error && error.message ? `：${error.message}` : ''
    throw new Error(`无法清除本机打卡记录${reason}`)
  }
}
