'use client'

import { useState, useRef, useCallback } from 'react'
import type { Destination } from '@/lib/constants'
import type { FolderEntry } from '@/lib/client-types'
import type { FileDiff } from '@/lib/types'
import { useSecret } from '@/hooks/useSecret'
import { useFolderEntries } from '@/hooks/useFolderEntries'
import SecretInput from './upload/SecretInput'
import DestinationPicker from './upload/DestinationPicker'
import FolderDropzone from './upload/FolderDropzone'
import FolderCard from './upload/FolderCard'
import ActionButtons from './upload/ActionButtons'
import OverwriteConfirmModal from './upload/OverwriteConfirmModal'
import NoChangesModal from './upload/NoChangesModal'
import DriveErrorModal from './upload/DriveErrorModal'

// ---------------------------------------------------------------------------
// API helpers
// ---------------------------------------------------------------------------
interface CheckResult {
  exists: boolean
  diffs: FileDiff[]
}

async function checkFolderDiffs(
  folder: FolderEntry,
  destination: string,
  secret: string,
  signal?: AbortSignal,
): Promise<CheckResult> {
  try {
    const params = new URLSearchParams({ folderName: folder.folderName, destination })
    const res = await fetch(`/api/upload/check?${params}`, {
      headers: { 'x-upload-secret': secret.trim() },
      signal,
    })
    const data = await res.json()
    if (!data.success || !data.exists) {
      return { exists: false, diffs: [] }
    }

    const remoteFiles: { name: string; size: number }[] = data.files || []
    const remoteMap = new Map(remoteFiles.map((f) => [f.name.toLowerCase(), f.size]))

    const diffs: FileDiff[] = []
    const localNames = new Set<string>()

    // Model: skip size comparison (compression changes the size).
    const modelKey = folder.modelFile.name.toLowerCase()
    localNames.add(modelKey)
    if (!remoteMap.has(modelKey)) {
      diffs.push({ name: folder.modelFile.name, status: 'new' })
    }

    // Textures: compare by size
    for (const tex of folder.textures) {
      const key = tex.name.toLowerCase()
      localNames.add(key)
      const remoteSize = remoteMap.get(key)
      if (remoteSize === undefined) {
        diffs.push({ name: tex.name, status: 'new' })
      } else if (remoteSize !== tex.file.size) {
        diffs.push({ name: tex.name, status: 'changed' })
      }
    }

    // Deleted
    for (const [name] of remoteMap) {
      if (!localNames.has(name)) {
        diffs.push({ name, status: 'deleted' })
      }
    }

    return { exists: true, diffs }
  } catch {
    return { exists: false, diffs: [] }
  }
}

/** Upload original files to Nextcloud Drive (no Blender compression). */
async function uploadDrive(
  folder: FolderEntry,
  secret: string,
  destination: string,
  action: 'new' | 'replace',
  signal?: AbortSignal,
): Promise<{ success: boolean; error?: string }> {
  const formData = new FormData()
  formData.append('folderName', folder.folderName)
  formData.append('destination', destination)
  formData.append('action', action)

  formData.append('files', folder.modelFile)
  formData.append('fileTypes', 'model')
  formData.append('textureNames', '')

  for (const tex of folder.textures) {
    formData.append('files', tex.file)
    formData.append('fileTypes', 'texture')
    formData.append('textureNames', tex.name)
  }

  try {
    const res = await fetch('/api/upload/drive', {
      method: 'POST',
      headers: { 'x-upload-secret': secret.trim() },
      body: formData,
      signal,
    })
    const data = await res.json()
    if (!data.success) return { success: false, error: data.error }
    return { success: true }
  } catch (err) {
    if (err instanceof DOMException && err.name === 'AbortError') {
      return { success: false, error: 'Upload annule' }
    }
    return { success: false, error: 'Erreur reseau (Drive)' }
  }
}

/** Upload files to GitHub (with Blender compression). */
async function uploadGit(
  folder: FolderEntry,
  secret: string,
  destination: string,
  onProgress: (pct: number) => void,
  signal?: AbortSignal,
): Promise<{ success: boolean; filename?: string; error?: string }> {
  const formData = new FormData()
  formData.append('folderName', folder.folderName)
  formData.append('destination', destination)

  formData.append('files', folder.modelFile)
  formData.append('fileTypes', 'model')
  formData.append('textureNames', '')

  for (const tex of folder.textures) {
    formData.append('files', tex.file)
    formData.append('fileTypes', 'texture')
    formData.append('textureNames', tex.name)
  }

  onProgress(10)

  try {
    const res = await fetch('/api/upload/git', {
      method: 'POST',
      headers: { 'x-upload-secret': secret.trim() },
      body: formData,
      signal,
    })

    onProgress(80)
    const data = await res.json()

    if (!data.success) {
      return { success: false, error: data.error }
    }

    onProgress(100)
    return { success: true, filename: folder.folderName }
  } catch (err) {
    if (err instanceof DOMException && err.name === 'AbortError') {
      return { success: false, error: 'Upload annule' }
    }
    return { success: false, error: 'Erreur reseau' }
  }
}

// ---------------------------------------------------------------------------
// UploadZone — orchestrator
// ---------------------------------------------------------------------------
export default function UploadZone() {
  const {
    secret,
    secretError,
    secretVisible,
    isSecretEmpty,
    setSecretError,
    handleSecretChange,
    toggleSecretVisible,
  } = useSecret()

  const {
    entries,
    setEntries,
    updateEntry,
    removeEntry,
    resetEntries,
    allDone,
    hasErrors,
  } = useFolderEntries()

  const [isUploading, setIsUploading] = useState(false)
  const [globalError, setGlobalError] = useState<string | null>(null)
  const [destination, setDestination] = useState<Destination | null>(null)
  const [overwriteConfirm, setOverwriteConfirm] = useState<{
    folderName: string
    diffs: FileDiff[]
  } | null>(null)
  const [noChangesFolder, setNoChangesFolder] = useState<string | null>(null)

  // Drive error modal state
  const [driveError, setDriveError] = useState<{
    error: string
    folderIndex: number
  } | null>(null)

  const abortRef = useRef<AbortController | null>(null)

  // Tracks the check result so we know if it's "new" or "replace" for the Drive
  const checkResultRef = useRef<CheckResult>({ exists: false, diffs: [] })

  // -- Handlers --

  const handleFolderSelected = (entry: FolderEntry) => {
    setGlobalError(null)
    setEntries([entry])
  }

  const handleToggleViewer = (index: number) => {
    const entry = entries[index]
    if (entry?.modelUrl) {
      updateEntry(index, { viewerOpen: !entry.viewerOpen })
    }
  }

  const handleUpload = async () => {
    if (!secret.trim()) {
      setSecretError("La cle d'acces est requise")
      return
    }
    if (!destination) {
      setGlobalError('Veuillez choisir une destination')
      return
    }
    if (entries.length === 0) return

    setSecretError(null)
    setGlobalError(null)

    const folder = entries[0]
    const check = await checkFolderDiffs(folder, destination!, secret, abortRef.current?.signal)
    checkResultRef.current = check

    if (check.exists) {
      if (check.diffs.length === 0) {
        setNoChangesFolder(folder.folderName)
        return
      }
      setOverwriteConfirm({ folderName: folder.folderName, diffs: check.diffs })
      return
    }

    await proceedUpload()
  }

  /**
   * Main upload flow: Drive first, then Git.
   * If Drive fails, show DriveErrorModal so user can decide.
   */
  const proceedUpload = async () => {
    setOverwriteConfirm(null)
    setIsUploading(true)
    setGlobalError(null)

    const controller = new AbortController()
    abortRef.current = controller

    for (let i = 0; i < entries.length; i++) {
      if (entries[i].status === 'success') continue
      if (controller.signal.aborted) break

      const folderEntry = entries[i]
      const driveAction = checkResultRef.current.exists ? 'replace' : 'new'

      // ---- Step 1: Drive upload ----
      updateEntry(i, {
        status: 'uploading',
        progress: 0,
        error: undefined,
        driveStatus: 'uploading',
        driveError: undefined,
      })

      const driveResult = await uploadDrive(
        folderEntry,
        secret,
        destination!,
        driveAction as 'new' | 'replace',
        controller.signal,
      )

      if (!driveResult.success) {
        // Drive failed — pause and ask user
        updateEntry(i, { driveStatus: 'error', driveError: driveResult.error })
        setDriveError({ error: driveResult.error || 'Erreur inconnue', folderIndex: i })
        // Stop here — the DriveErrorModal callbacks will resume or cancel
        return
      }

      updateEntry(i, { driveStatus: 'success' })

      // ---- Step 2: Git upload ----
      await pushGit(i, controller.signal)
    }

    abortRef.current = null
    setIsUploading(false)
  }

  /** Push a single folder to Git. Called after Drive succeeds or user skips Drive. */
  const pushGit = async (index: number, signal?: AbortSignal) => {
    const folderEntry = entries[index]

    updateEntry(index, { progress: 5 })

    const gitResult = await uploadGit(
      folderEntry,
      secret,
      destination!,
      (pct) => updateEntry(index, { progress: pct }),
      signal,
    )

    updateEntry(index, {
      status: gitResult.success ? 'success' : 'error',
      progress: gitResult.success ? 100 : 0,
      error: gitResult.success ? undefined : gitResult.error,
      filename: gitResult.filename,
    })
  }

  /** User chose "Continue without Drive" in DriveErrorModal. */
  const handleDriveContinue = useCallback(async () => {
    if (!driveError) return
    const idx = driveError.folderIndex
    setDriveError(null)

    updateEntry(idx, { driveStatus: 'skipped' })

    // Continue with Git
    const signal = abortRef.current?.signal
    await pushGit(idx, signal)

    // Continue with remaining entries
    for (let i = idx + 1; i < entries.length; i++) {
      if (entries[i].status === 'success') continue
      if (abortRef.current?.signal.aborted) break

      // For subsequent entries after a Drive skip, also skip Drive
      updateEntry(i, {
        status: 'uploading',
        progress: 0,
        error: undefined,
        driveStatus: 'skipped',
      })

      await pushGit(i, abortRef.current?.signal)
    }

    abortRef.current = null
    setIsUploading(false)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [driveError, entries, secret, destination])

  /** User chose "Cancel" in DriveErrorModal. */
  const handleDriveCancel = useCallback(() => {
    if (!driveError) return
    const idx = driveError.folderIndex
    setDriveError(null)

    updateEntry(idx, {
      status: 'error',
      progress: 0,
      error: 'Upload annule (Drive echoue)',
      driveStatus: 'error',
    })

    abortRef.current?.abort()
    abortRef.current = null
    setIsUploading(false)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [driveError])

  const handleCancel = () => {
    abortRef.current?.abort()
    abortRef.current = null
    setIsUploading(false)
  }

  const handleReset = () => {
    resetEntries()
    setGlobalError(null)
    setIsUploading(false)
    setDriveError(null)
    checkResultRef.current = { exists: false, diffs: [] }
  }

  const hasPendingOrErrors = entries.some((f) => f.status === 'pending' || f.status === 'error')

  // -- Render --

  return (
    <div className="w-full max-w-2xl space-y-4">
      <SecretInput
        secret={secret}
        secretVisible={secretVisible}
        secretError={secretError}
        disabled={isUploading}
        onChange={handleSecretChange}
        onToggleVisible={toggleSecretVisible}
      />

      <DestinationPicker
        destination={destination}
        disabled={isUploading}
        onChange={setDestination}
      />

      {entries.length === 0 && (
        <FolderDropzone
          isUploading={isUploading}
          onFolderSelected={handleFolderSelected}
          onError={setGlobalError}
        />
      )}

      {globalError && (
        <p className="text-xs text-red-400 text-center">{globalError}</p>
      )}

      {entries.length > 0 && (
        <div className="space-y-2">
          {entries.map((entry, i) => (
            <FolderCard
              key={i}
              entry={entry}
              index={i}
              onToggleViewer={handleToggleViewer}
              onRemove={removeEntry}
            />
          ))}
        </div>
      )}

      <ActionButtons
        isUploading={isUploading}
        isSecretEmpty={isSecretEmpty}
        noDestination={!destination}
        hasPendingOrErrors={hasPendingOrErrors}
        allDone={allDone}
        hasErrors={hasErrors}
        onUpload={handleUpload}
        onCancel={handleCancel}
        onReset={handleReset}
      />

      {overwriteConfirm && (
        <OverwriteConfirmModal
          destination={destination!}
          folderName={overwriteConfirm.folderName}
          diffs={overwriteConfirm.diffs}
          onCancel={() => setOverwriteConfirm(null)}
          onConfirm={proceedUpload}
        />
      )}

      {noChangesFolder && (
        <NoChangesModal
          destination={destination!}
          folderName={noChangesFolder}
          onCancel={() => {
            setNoChangesFolder(null)
            handleReset()
          }}
          onModify={() => setNoChangesFolder(null)}
        />
      )}

      {driveError && (
        <DriveErrorModal
          error={driveError.error}
          onCancel={handleDriveCancel}
          onContinue={handleDriveContinue}
        />
      )}
    </div>
  )
}
