'use client'

import JSZip from 'jszip'

import {
  collectPhotos,
  generateCertificateAsset,
  generateIdPhoto,
  loadImageSource,
  loadImageSourceFromDataUrl,
  loadImageSourceFromUrl,
  parseStudentFile,
  type ImageSource
} from '@/lib/certificate-browser'
import {
  DEFAULT_TEMPLATE_CONFIG,
  DEFAULT_ID_PHOTO_CONFIG,
  DEFAULT_ID_PHOTO_CROP,
  type CertificateTemplateConfig,
  type GeneratedAsset,
  type IdPhotoConfig,
  type IdPhotoCrop,
  type StudentRow,
  normalizeMatchName,
  resolveIdPhotoCrop,
  resolveIdPhotoConfig,
  sanitizeFileName
} from '@/lib/certificate-types'
import type {
  PersistedAppState,
  PersistedClassItem,
  PersistedGeneratedCertificate,
  PersistedImageSource,
  PersistedTemplateItem
} from '@/lib/storage-types'

export type ActiveTool = 'image' | 'name'

export type ClassStudent = {
  id: string
  name: string
  photo?: ImageSource
  photoFileName?: string
  idPhoto?: ImageSource
  idPhotoFileName?: string
  idPhotoCrop?: IdPhotoCrop
}

export type ClassItem = {
  id: string
  name: string
  students: ClassStudent[]
  idPhotoConfig: IdPhotoConfig
  createdAt: string
}

export type TemplateItem = {
  id: string
  name: string
  image: ImageSource
  config: CertificateTemplateConfig
  updatedAt: string
}

export type GeneratedCertificate = {
  id: string
  classId: string
  className: string
  studentId: string
  studentName: string
  photo: ImageSource
  template: TemplateItem
  asset: GeneratedAsset
  jpgUrl: string
  updatedAt: string
}

const DEFAULT_TEMPLATE_URL = '/default-certificate.jpg'

export function createId(prefix: string) {
  return `${prefix}-${crypto.randomUUID()}`
}

export function fileList(input: HTMLInputElement) {
  return Array.from(input.files ?? [])
}

export function downloadBlob(blob: Blob, fileName: string) {
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = fileName
  link.click()
  URL.revokeObjectURL(url)
}

export function downloadText(text: string, fileName: string) {
  downloadBlob(new Blob([text], { type: 'application/json;charset=utf-8' }), fileName)
}

function readBlobAsDataUrl(blob: Blob) {
  return new Promise<string>((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => resolve(String(reader.result))
    reader.onerror = () => reject(new Error('文件读取失败'))
    reader.readAsDataURL(blob)
  })
}

function blobFromDataUrl(dataUrl: string) {
  const mimeType = dataUrl.match(/^data:([^;]+);base64,/)?.[1] ?? 'application/octet-stream'
  const base64 = dataUrl.split(',')[1] ?? ''
  const bytes = Uint8Array.from(atob(base64), (character) => character.charCodeAt(0))

  return new Blob([bytes], { type: mimeType })
}

export async function blobFromSource(source: string) {
  if (source.startsWith('data:')) {
    return blobFromDataUrl(source)
  }

  const response = await fetch(source)

  if (!response.ok) {
    throw new Error('证书图片加载失败')
  }

  return response.blob()
}

export function persistImageSource(source: ImageSource): PersistedImageSource {
  const { blob: _blob, ...persistedSource } = source

  return persistedSource
}

export async function hydrateImageSource(source?: PersistedImageSource | ImageSource) {
  if (!source?.dataUrl) {
    return undefined
  }

  if (!source.dataUrl.startsWith('data:')) {
    return loadImageSourceFromUrl(
      source.dataUrl,
      source.fileName,
      source.width,
      source.height,
      source.contentType
    )
  }

  return loadImageSourceFromDataUrl(source.dataUrl, source.fileName)
}

export async function loadDefaultTemplateImage() {
  const response = await fetch(DEFAULT_TEMPLATE_URL)

  if (!response.ok) {
    throw new Error('默认模板底图加载失败')
  }

  return loadImageSource(await response.blob(), 'default-certificate.jpg')
}

function isLegacyDefaultCmykTemplate(template: TemplateItem | PersistedTemplateItem) {
  return (
    template.name === '亲爱版毕业证书' &&
    template.image.width === DEFAULT_TEMPLATE_CONFIG.templateWidth &&
    template.image.height === DEFAULT_TEMPLATE_CONFIG.templateHeight &&
    template.image.dataUrl.startsWith('data:image/jpeg') &&
    template.image.dataUrl.includes('Q01ZS0xhYi')
  )
}

export async function hydrateTemplate(
  template: TemplateItem | PersistedTemplateItem
): Promise<TemplateItem> {
  const image = isLegacyDefaultCmykTemplate(template)
    ? await loadDefaultTemplateImage()
    : await hydrateImageSource(template.image)

  if (!image) {
    throw new Error('模板底图加载失败')
  }

  return {
    ...template,
    image,
    config: {
      ...template.config,
      templateWidth: image.width,
      templateHeight: image.height
    }
  }
}

export function hydrateClassMetadata(classItem: PersistedClassItem): ClassItem {
  return {
    ...classItem,
    idPhotoConfig: resolveIdPhotoConfig(classItem.idPhotoConfig),
    students: classItem.students.map((student) => ({
      ...student,
      photo: undefined,
      idPhoto: undefined
    }))
  }
}

export async function hydrateClassItem(classItem: PersistedClassItem): Promise<ClassItem> {
  return {
    ...classItem,
    idPhotoConfig: resolveIdPhotoConfig(classItem.idPhotoConfig),
    students: await Promise.all(
      classItem.students.map(async (student) => ({
        ...student,
        photo: await hydrateImageSource(student.photo),
        idPhoto: await hydrateImageSource(student.idPhoto)
      }))
    )
  }
}

export async function hydrateCertificate(
  certificate: PersistedGeneratedCertificate
): Promise<GeneratedCertificate> {
  const [photo, template] = await Promise.all([
    hydrateImageSource(certificate.photo),
    hydrateTemplate(certificate.template)
  ])

  if (!photo) {
    throw new Error('证书照片加载失败')
  }

  if (!certificate.jpgUrl) {
    throw new Error('证书图片加载失败')
  }

  return {
    id: certificate.id,
    classId: certificate.classId,
    className: certificate.className,
    studentId: certificate.studentId,
    studentName: certificate.studentName,
    photo,
    template,
    asset: {
      fileName: certificate.asset.fileName || sanitizeFileName(certificate.studentName)
    },
    jpgUrl: certificate.jpgUrl,
    updatedAt: certificate.updatedAt
  }
}

export async function loadAppState() {
  const response = await fetch('/api/storage')

  if (!response.ok) {
    throw new Error('SQLite 数据读取失败')
  }

  return (await response.json()) as PersistedAppState
}

export async function saveAppState(state: PersistedAppState) {
  const response = await fetch('/api/storage', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(state)
  })

  if (!response.ok) {
    throw new Error('SQLite 保存失败')
  }
}

export async function persistGeneratedCertificate(
  certificate: GeneratedCertificate
): Promise<PersistedGeneratedCertificate> {
  return {
    ...certificate,
    photo: persistImageSource(certificate.photo),
    template: {
      ...certificate.template,
      image: persistImageSource(certificate.template.image)
    },
    asset: {
      fileName: certificate.asset.fileName
    },
    jpgUrl: certificate.asset.jpg
      ? await readBlobAsDataUrl(certificate.asset.jpg)
      : certificate.jpgUrl
  }
}

export function persistClasses(
  classes: ClassItem[],
  fallbackState?: PersistedAppState
): PersistedClassItem[] {
  const fallbackStudentsById = new Map(
    (fallbackState?.classes ?? []).flatMap((classItem) =>
      classItem.students.map((student) => [student.id, student])
    )
  )

  return classes.map((classItem) => ({
    ...classItem,
    idPhotoConfig: resolveIdPhotoConfig(classItem.idPhotoConfig),
    students: classItem.students.map((student) => ({
      ...student,
      photo: student.photo
        ? persistImageSource(student.photo)
        : fallbackStudentsById.get(student.id)?.photo,
      photoFileName: student.photoFileName ?? fallbackStudentsById.get(student.id)?.photoFileName,
      idPhoto: student.idPhoto
        ? persistImageSource(student.idPhoto)
        : fallbackStudentsById.get(student.id)?.idPhoto,
      idPhotoFileName: student.idPhotoFileName ?? fallbackStudentsById.get(student.id)?.idPhotoFileName,
      idPhotoCrop: student.idPhotoCrop
        ? resolveIdPhotoCrop(student.idPhotoCrop)
        : fallbackStudentsById.get(student.id)?.idPhotoCrop
    }))
  }))
}

export async function parseRosterFromClassZip(zipFile: File): Promise<StudentRow[]> {
  const zip = await JSZip.loadAsync(await zipFile.arrayBuffer())
  const rosterEntry = Object.values(zip.files).find(
    (entry) => !entry.dir && /\.(xlsx|csv)$/i.test(entry.name)
  )

  if (rosterEntry) {
    const rosterFile = new File([await rosterEntry.async('blob')], rosterEntry.name)
    const parsed = await parseStudentFile(rosterFile)

    if (parsed.error) {
      throw new Error(parsed.error)
    }

    return parsed.rows
  }

  const imageNames = Object.values(zip.files)
    .filter((entry) => !entry.dir && /\.(jpe?g|png)$/i.test(entry.name))
    .map((entry, index) => ({
      name: sanitizeFileName(entry.name.split(/[\\/]/).pop()?.replace(/\.[^.]+$/, '') ?? ''),
      rowNumber: index + 1
    }))
    .filter((row) => row.name)

  if (imageNames.length === 0) {
    throw new Error('ZIP 内需要包含 Excel/CSV 名单或按姓名命名的照片')
  }

  return imageNames
}

export async function studentsFromClassZip(file: File) {
  const rows = await parseRosterFromClassZip(file)
  const { photos, issues } = await collectPhotos([], [file])
  const students: ClassStudent[] = rows.map((row) => {
    const photo = photos.get(normalizeMatchName(row.name))

    return {
      id: createId('student'),
      name: row.name,
      photo,
      photoFileName: photo?.fileName
    }
  })

  return { students, issues }
}

export async function ensureStudentIdPhoto(student: ClassStudent, config: IdPhotoConfig, force = false) {
  if (!student.photo) {
    return student
  }

  if (
    !force &&
    student.idPhoto &&
    student.idPhoto.width === config.widthPx &&
    student.idPhoto.height === config.heightPx
  ) {
    return student
  }

  try {
    const crop = resolveIdPhotoCrop(student.idPhotoCrop ?? DEFAULT_ID_PHOTO_CROP)
    const idPhoto = await generateIdPhoto(student.photo, resolveIdPhotoConfig(config), crop)

    return {
      ...student,
      idPhoto,
      idPhotoFileName: idPhoto.fileName,
      idPhotoCrop: crop
    }
  } catch (error) {
    console.warn(`证件照生成失败：${student.name}`, error)
    return student
  }
}

export async function ensureClassIdPhotos(classItem: ClassItem, force = false) {
  const idPhotoConfig = resolveIdPhotoConfig(classItem.idPhotoConfig ?? DEFAULT_ID_PHOTO_CONFIG)

  return {
    ...classItem,
    idPhotoConfig,
    students: await Promise.all(
      classItem.students.map((student) => ensureStudentIdPhoto(student, idPhotoConfig, force))
    )
  }
}

export async function generateForStudent(
  classItem: ClassItem,
  student: ClassStudent,
  template: TemplateItem
) {
  const certificatePhoto = student.idPhoto

  if (!certificatePhoto) {
    return null
  }

  const asset = await generateCertificateAsset(
    template.image,
    certificatePhoto,
    student.name,
    template.config
  )

  return {
    id: createId('certificate'),
    classId: classItem.id,
    className: classItem.name,
    studentId: student.id,
    studentName: student.name,
    photo: student.photo ?? certificatePhoto,
    template,
    asset,
    jpgUrl: URL.createObjectURL(asset.jpg),
    updatedAt: new Date().toISOString()
  } satisfies GeneratedCertificate
}

export function createResultReport(certificates: GeneratedCertificate[], classItem: ClassItem) {
  const generatedIds = new Set(certificates.map((certificate) => certificate.studentId))
  const lines = ['姓名,状态,说明']

  for (const student of classItem.students) {
    lines.push(
      generatedIds.has(student.id)
        ? `${student.name},success,已生成`
        : `${student.name},skipped,缺少照片或生成失败`
    )
  }

  return `\uFEFF${lines.join('\n')}`
}
