import { useState, useMemo, useEffect } from 'react'
import {
  X,
  Star,
  MapPin,
  Camera,
  Aperture,
  Info,
  ChevronDown,
  ChevronRight,
  ShoppingBasket,
  Trash2,
} from 'lucide-react'
import clsx from 'clsx'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { format } from 'date-fns'
import { usePhotoStore } from '../../store/photoStore'
import { photos as photosApi, heaps as heapsApi } from '../../services/api'
import { useActiveHeapMembers } from '../../hooks/useActiveHeapMembersQuery'
import { HEAPS_QUERY_KEY } from '../../hooks/useHeapsQuery'
import { useTagsQuery, TAGS_QUERY_KEY } from '../../hooks/useTagsQuery'
import { tags as tagsApi, type Tag } from '../../services/api'
import { toast } from '../ToastContainer'

interface PhotoTagSummary {
  id: string
  name: string
  color: string | null
}

interface PhotoDetails {
  id: string
  filename: string
  filepath: string
  width: number | null
  height: number | null
  file_size: number | null
  taken_at: string | null
  rating: number
  is_discarded: boolean
  user_title: string | null
  user_notes: string | null
  color_label: string | null
  exif_json: string | null
  tags?: PhotoTagSummary[]
}

type ColorLabel = 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple'

const COLOR_LABEL_OPTIONS: { value: ColorLabel; className: string }[] = [
  { value: 'red', className: 'bg-red-500' },
  { value: 'orange', className: 'bg-orange-500' },
  { value: 'yellow', className: 'bg-yellow-400' },
  { value: 'green', className: 'bg-green-500' },
  { value: 'blue', className: 'bg-blue-500' },
  { value: 'purple', className: 'bg-purple-500' },
]

interface ExifData {
  Make?: string
  Model?: string
  LensModel?: string
  Lens?: string
  ISO?: number | string
  FNumber?: number | string
  ApertureValue?: number | string
  ExposureTime?: string
  ShutterSpeedValue?: string
  FocalLength?: string
  FocalLengthIn35mmFormat?: string
  GPSLatitude?: number | string
  GPSLongitude?: number | string
  [key: string]: unknown
}

function formatFileSize(bytes: number | null): string {
  if (bytes == null) return '—'
  if (bytes < 1024) return `${bytes} B`
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`
  return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`
}

function formatExifValue(v: unknown): string {
  if (v == null || v === '') return '—'
  return String(v)
}

function pickFirst(exif: ExifData, ...keys: string[]): string {
  for (const k of keys) {
    const v = exif[k]
    if (v != null && v !== '') return String(v)
  }
  return '—'
}

function parseExif(json: string | null): ExifData {
  if (!json) return {}
  try {
    const parsed = JSON.parse(json)
    return typeof parsed === 'object' && parsed !== null ? (parsed as ExifData) : {}
  } catch {
    return {}
  }
}

export function RightSidebar() {
  const { selectedPhotos, activePhotoId, clearSelection } = usePhotoStore()
  const queryClient = useQueryClient()

  const [expandedSections, setExpandedSections] = useState<Set<string>>(
    new Set(['basic', 'camera', 'location', 'tags'])
  )

  const toggleSection = (section: string) => {
    const newExpanded = new Set(expandedSections)
    if (newExpanded.has(section)) newExpanded.delete(section)
    else newExpanded.add(section)
    setExpandedSections(newExpanded)
  }

  // Fetch the active photo's full record (with EXIF) on demand.
  const { data: photo } = useQuery<PhotoDetails>({
    queryKey: ['photo', activePhotoId],
    queryFn: () => photosApi.get(activePhotoId!),
    enabled: !!activePhotoId,
    staleTime: 60_000,
  })

  // Mutation for any patchable field on the active photo. Invalidates both
  // the photo detail cache and the timeline list so the grid reflects the
  // change too.
  const updateMutation = useMutation({
    mutationFn: (data: {
      filename?: string
      rating?: number
      is_discarded?: boolean
      user_title?: string | null
      user_notes?: string | null
      color_label?: string | null
    }) => photosApi.update(activePhotoId!, data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['photo', activePhotoId] })
      queryClient.invalidateQueries({ queryKey: ['photos'] })
    },
  })

  // Membership in the active heap (for the Pick toggle button).
  const { activeHeap, memberIds: activeHeapMembers } = useActiveHeapMembers()
  const isInActiveHeap =
    !!activePhotoId && activeHeapMembers.has(activePhotoId)

  const heapMutation = useMutation({
    mutationFn: ({ remove }: { remove: boolean }) => {
      if (!activeHeap || !activePhotoId) return Promise.resolve(null)
      return remove
        ? heapsApi.removePhotos(activeHeap.id, [activePhotoId])
        : heapsApi.addPhotos(activeHeap.id, [activePhotoId])
    },
    // Optimistic flip so the badge / button label update instantly.
    onMutate: ({ remove }) => {
      if (!activeHeap || !activePhotoId) return { previous: undefined }
      const key = ['heap-photo-ids', activeHeap.id] as const
      const previous = queryClient.getQueryData<string[]>(key)
      const set = new Set(previous ?? [])
      if (remove) set.delete(activePhotoId)
      else set.add(activePhotoId)
      queryClient.setQueryData<string[]>(key, Array.from(set))
      return { previous }
    },
    onError: (_e, _vars, ctx) => {
      if (activeHeap && ctx?.previous) {
        queryClient.setQueryData(['heap-photo-ids', activeHeap.id], ctx.previous)
      }
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: HEAPS_QUERY_KEY })
      if (activeHeap) {
        queryClient.invalidateQueries({
          queryKey: ['heap-photo-ids', activeHeap.id],
        })
      }
    },
  })

  // ── Tags state + mutations ──────────────────────────────────────────
  const { data: allTags = [] } = useTagsQuery()
  const [tagInput, setTagInput] = useState('')

  const invalidateTagsAndPhoto = () => {
    queryClient.invalidateQueries({ queryKey: TAGS_QUERY_KEY })
    queryClient.invalidateQueries({ queryKey: ['photo', activePhotoId] })
    queryClient.invalidateQueries({ queryKey: ['photos'] })
  }

  const addTagMutation = useMutation({
    mutationFn: async (name: string) => {
      // Idempotent create — backend returns existing row if name matches.
      const created = await tagsApi.create(name)
      if (activePhotoId) {
        await tagsApi.addToPhoto(activePhotoId, [created.id])
      }
      return created
    },
    onSuccess: () => invalidateTagsAndPhoto(),
    onError: (e: any) =>
      toast.error('Add tag failed', e?.message || 'Unknown error'),
  })

  const attachExistingTagMutation = useMutation({
    mutationFn: (tagId: string) =>
      tagsApi.addToPhoto(activePhotoId!, [tagId]),
    onSuccess: () => invalidateTagsAndPhoto(),
    onError: (e: any) =>
      toast.error('Add tag failed', e?.message || 'Unknown error'),
  })

  const removeTagMutation = useMutation({
    mutationFn: (tagId: string) =>
      tagsApi.removeFromPhoto(activePhotoId!, tagId),
    onSuccess: () => invalidateTagsAndPhoto(),
    onError: (e: any) =>
      toast.error('Remove tag failed', e?.message || 'Unknown error'),
  })

  // Local drafts for the editable text fields. These mirror the server value
  // but stay independent while the user is typing, so we don't fight focus or
  // clobber edits with stale refetches.
  const [filenameDraft, setFilenameDraft] = useState('')
  const [titleDraft, setTitleDraft] = useState('')
  const [notesDraft, setNotesDraft] = useState('')

  useEffect(() => {
    setFilenameDraft(photo?.filename ?? '')
    setTitleDraft(photo?.user_title ?? '')
    setNotesDraft(photo?.user_notes ?? '')
  }, [photo?.id, photo?.filename, photo?.user_title, photo?.user_notes])

  const commitFilename = () => {
    const next = filenameDraft.trim()
    const current = photo?.filename ?? ''
    if (!next || next === current) {
      // Reset draft if user cleared it; we never send an empty filename.
      setFilenameDraft(current)
      return
    }
    if (next.includes('/') || next.includes('\\') || next === '.' || next === '..') {
      toast.error('Invalid filename', 'No path separators allowed')
      setFilenameDraft(current)
      return
    }
    updateMutation.mutate(
      { filename: next },
      {
        onError: (e: any) => {
          toast.error(
            'Rename failed',
            e?.response?.data?.detail || e.message || 'Unknown error'
          )
          setFilenameDraft(current)
        },
      }
    )
  }

  const commitTitle = () => {
    const next = titleDraft.trim()
    const current = photo?.user_title ?? ''
    if (next === current) return
    updateMutation.mutate({ user_title: next || null })
  }

  const commitNotes = () => {
    const next = notesDraft
    const current = photo?.user_notes ?? ''
    if (next === current) return
    updateMutation.mutate({ user_notes: next || null })
  }

  const setColor = (label: ColorLabel | null) => {
    updateMutation.mutate({ color_label: label })
  }

  const exif = useMemo(() => parseExif(photo?.exif_json ?? null), [photo?.exif_json])

  if (selectedPhotos.length === 0) {
    return (
      <div className="flex h-full items-center justify-center p-4 text-center">
        <div className="text-text-muted">
          <Info className="mx-auto mb-2 h-8 w-8" />
          <p className="text-sm">Select photos to view details</p>
        </div>
      </div>
    )
  }

  const multipleSelected = selectedPhotos.length > 1
  const rating = photo?.rating ?? 0
  const isDiscarded = photo?.is_discarded ?? false
  const colorLabel = (photo?.color_label ?? null) as ColorLabel | null

  return (
    <div className="flex h-full flex-col bg-surface">
      {/* Header */}
      <div className="flex items-center justify-between border-b border-border px-4 py-3">
        <h2 className="text-sm font-semibold text-text">
          {multipleSelected
            ? `${selectedPhotos.length} Photos Selected`
            : 'Photo Details'}
        </h2>
        <button
          onClick={clearSelection}
          className="rounded p-1 text-text-muted hover:bg-surface-2 hover:text-text"
          title="Clear selection"
        >
          <X className="h-4 w-4" />
        </button>
      </div>

      {/* Quick Actions — operate on the active photo */}
      {photo && !multipleSelected && (
        <div className="space-y-3 border-b border-border p-4">
          {/* Filename (editable, renames the file on disk) */}
          <div>
            <label className="mb-1 block text-xs text-text-muted">Filename</label>
            <input
              type="text"
              value={filenameDraft}
              onChange={(e) => setFilenameDraft(e.target.value)}
              onBlur={commitFilename}
              onKeyDown={(e) => {
                if (e.key === 'Enter') {
                  e.currentTarget.blur()
                } else if (e.key === 'Escape') {
                  setFilenameDraft(photo.filename ?? '')
                  e.currentTarget.blur()
                }
              }}
              className="w-full rounded border border-border bg-bg px-2 py-1 font-mono text-xs text-text focus:border-primary focus:outline-none"
            />
          </div>

          {/* Title (editable) */}
          <div>
            <label className="mb-1 block text-xs text-text-muted">Title</label>
            <input
              type="text"
              value={titleDraft}
              onChange={(e) => setTitleDraft(e.target.value)}
              onBlur={commitTitle}
              onKeyDown={(e) => {
                if (e.key === 'Enter') {
                  e.currentTarget.blur()
                } else if (e.key === 'Escape') {
                  setTitleDraft(photo.user_title ?? '')
                  e.currentTarget.blur()
                }
              }}
              placeholder="No title"
              className="w-full rounded border border-border bg-bg px-2 py-1 text-sm text-text placeholder-text-faint focus:border-primary focus:outline-none"
            />
          </div>

          {/* Notes (editable) */}
          <div>
            <label className="mb-1 block text-xs text-text-muted">Notes</label>
            <textarea
              value={notesDraft}
              onChange={(e) => setNotesDraft(e.target.value)}
              onBlur={commitNotes}
              placeholder="Add notes…"
              rows={3}
              className="w-full resize-none rounded border border-border bg-bg px-2 py-1 text-sm text-text placeholder-text-faint focus:border-primary focus:outline-none"
            />
          </div>

          {/* Rating */}
          <div>
            <label className="mb-1 block text-xs text-text-muted">Rating</label>
            <div className="flex gap-1">
              {[1, 2, 3, 4, 5].map((value) => (
                <button
                  key={value}
                  onClick={() =>
                    updateMutation.mutate({ rating: rating === value ? 0 : value })
                  }
                  className="p-0.5"
                  title={`Set rating to ${value}`}
                >
                  <Star
                    className={clsx(
                      'h-5 w-5 transition-colors',
                      value <= rating
                        ? 'fill-star text-star'
                        : 'text-text-muted hover:text-star'
                    )}
                  />
                </button>
              ))}
            </div>
          </div>

          {/* Color label */}
          <div>
            <label className="mb-1 block text-xs text-text-muted">Color label</label>
            <div className="flex items-center gap-1.5">
              {COLOR_LABEL_OPTIONS.map(({ value, className }) => {
                const active = colorLabel === value
                return (
                  <button
                    key={value}
                    onClick={() => setColor(active ? null : value)}
                    className={clsx(
                      'h-5 w-5 rounded-full ring-offset-2 ring-offset-surface transition-all',
                      className,
                      active ? 'ring-2 ring-primary' : 'opacity-60 hover:opacity-100'
                    )}
                    title={value}
                  />
                )
              })}
              {colorLabel && (
                <button
                  onClick={() => setColor(null)}
                  className="ml-1 rounded p-0.5 text-text-muted hover:bg-surface-2 hover:text-text"
                  title="Clear color label"
                >
                  <X className="h-3 w-3" />
                </button>
              )}
            </div>
          </div>

          {/* Flag */}
          <div>
            <label className="mb-1 block text-xs text-text-muted">Flag</label>
            <div className="flex gap-2">
              <button
                onClick={() => heapMutation.mutate({ remove: isInActiveHeap })}
                disabled={!activeHeap || heapMutation.isPending}
                className={clsx(
                  'flex items-center gap-1 rounded px-2 py-1 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50',
                  isInActiveHeap
                    ? 'bg-pick/20 text-pick'
                    : 'bg-surface-2 text-text-muted hover:bg-surface-offset'
                )}
                title={
                  activeHeap
                    ? isInActiveHeap
                      ? `Remove from "${activeHeap.name}"`
                      : `Add to "${activeHeap.name}"`
                    : 'Set an active heap first'
                }
              >
                <ShoppingBasket className="h-3 w-3" />
                {isInActiveHeap ? 'Picked' : 'Pick'}
              </button>
              <button
                onClick={() =>
                  updateMutation.mutate({ is_discarded: !isDiscarded })
                }
                className={clsx(
                  'flex items-center gap-1 rounded px-2 py-1 text-sm transition-colors',
                  isDiscarded
                    ? 'bg-reject/20 text-reject'
                    : 'bg-surface-2 text-text-muted hover:bg-surface-offset'
                )}
              >
                <Trash2 className="h-3 w-3" />
                Discard
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Metadata */}
      <div className="flex-1 overflow-y-auto">
        {photo && !multipleSelected && (
          <>
            {/* Basic Info */}
            <Section
              title="Basic Info"
              expanded={expandedSections.has('basic')}
              onToggle={() => toggleSection('basic')}
            >
              <div className="grid grid-cols-2 gap-2 text-xs">
                <Field label="Size" value={formatFileSize(photo.file_size)} />
                <Field
                  label="Dimensions"
                  value={
                    photo.width && photo.height
                      ? `${photo.width} × ${photo.height}`
                      : '—'
                  }
                />
                <Field
                  label="Date Taken"
                  value={
                    photo.taken_at
                      ? format(new Date(photo.taken_at), 'MMM d, yyyy HH:mm')
                      : '—'
                  }
                />
              </div>
            </Section>

            {/* Camera */}
            <Section
              title="Camera"
              expanded={expandedSections.has('camera')}
              onToggle={() => toggleSection('camera')}
            >
              <div className="space-y-1 text-xs">
                <div className="flex items-center gap-2">
                  <Camera className="h-3 w-3 text-text-muted" />
                  <span className="text-text">
                    {pickFirst(exif, 'Make', 'Model') === '—'
                      ? '—'
                      : `${formatExifValue(exif.Make)} ${formatExifValue(exif.Model)}`.trim()}
                  </span>
                </div>
                <div className="flex items-center gap-2">
                  <Aperture className="h-3 w-3 text-text-muted" />
                  <span className="text-text">
                    {pickFirst(exif, 'LensModel', 'Lens')}
                  </span>
                </div>
                <div className="mt-2 grid grid-cols-2 gap-2">
                  <Field label="ISO" value={formatExifValue(exif.ISO)} />
                  <Field
                    label="Aperture"
                    value={
                      exif.FNumber
                        ? `f/${exif.FNumber}`
                        : pickFirst(exif, 'ApertureValue')
                    }
                  />
                  <Field
                    label="Shutter"
                    value={pickFirst(exif, 'ExposureTime', 'ShutterSpeedValue')}
                  />
                  <Field
                    label="Focal"
                    value={pickFirst(
                      exif,
                      'FocalLength',
                      'FocalLengthIn35mmFormat'
                    )}
                  />
                </div>
              </div>
            </Section>

            {/* Location */}
            <Section
              title="Location"
              expanded={expandedSections.has('location')}
              onToggle={() => toggleSection('location')}
            >
              {exif.GPSLatitude && exif.GPSLongitude ? (
                <div className="flex items-center gap-2 text-xs">
                  <MapPin className="h-3 w-3 text-text-muted" />
                  <span className="font-mono text-text">
                    {String(exif.GPSLatitude)}, {String(exif.GPSLongitude)}
                  </span>
                </div>
              ) : (
                <div className="text-xs text-text-muted">No GPS data</div>
              )}
            </Section>

            {/* Tags */}
            <Section
              title="Tags"
              expanded={expandedSections.has('tags')}
              onToggle={() => toggleSection('tags')}
            >
              <TagsEditor
                photoTags={photo.tags ?? []}
                allTags={allTags}
                tagInput={tagInput}
                onTagInputChange={setTagInput}
                onAttachExisting={(id) => attachExistingTagMutation.mutate(id)}
                onCreateAndAttach={(name) => {
                  addTagMutation.mutate(name)
                  setTagInput('')
                }}
                onRemove={(id) => removeTagMutation.mutate(id)}
              />
            </Section>
          </>
        )}

        {!photo && !multipleSelected && (
          <div className="p-4 text-xs text-text-muted">Loading…</div>
        )}
      </div>

      {/* Footer Actions for multi-select */}
      {multipleSelected && (
        <div className="border-t border-border p-3">
          <div className="space-y-2">
            <button className="w-full rounded bg-surface-2 px-3 py-1.5 text-sm text-text hover:bg-surface-offset">
              Add to Heap
            </button>
            <button className="w-full rounded bg-surface-2 px-3 py-1.5 text-sm text-text hover:bg-surface-offset">
              Export Selected
            </button>
          </div>
        </div>
      )}
    </div>
  )
}

function Section({
  title,
  expanded,
  onToggle,
  children,
}: {
  title: string
  expanded: boolean
  onToggle: () => void
  children: React.ReactNode
}) {
  return (
    <div className="border-b border-border">
      <button
        onClick={onToggle}
        className="flex w-full items-center justify-between px-4 py-2 text-sm hover:bg-surface-2"
      >
        <span className="font-medium text-text">{title}</span>
        {expanded ? (
          <ChevronDown className="h-4 w-4 text-text-muted" />
        ) : (
          <ChevronRight className="h-4 w-4 text-text-muted" />
        )}
      </button>
      {expanded && <div className="px-4 pb-3">{children}</div>}
    </div>
  )
}

interface TagsEditorProps {
  photoTags: PhotoTagSummary[]
  allTags: Tag[]
  tagInput: string
  onTagInputChange: (value: string) => void
  onAttachExisting: (id: string) => void
  onCreateAndAttach: (name: string) => void
  onRemove: (id: string) => void
}

function TagsEditor({
  photoTags,
  allTags,
  tagInput,
  onTagInputChange,
  onAttachExisting,
  onCreateAndAttach,
  onRemove,
}: TagsEditorProps) {
  const trimmed = tagInput.trim()
  const lowerTrimmed = trimmed.toLowerCase()
  const photoTagIds = new Set(photoTags.map((t) => t.id))

  // Suggestions: tags whose name contains the input AND that aren't
  // already on the photo. Capped at 6 to keep the dropdown short.
  const suggestions = trimmed
    ? allTags
        .filter(
          (t) =>
            !photoTagIds.has(t.id) &&
            t.name.toLowerCase().includes(lowerTrimmed)
        )
        .slice(0, 6)
    : []

  const exactMatch = trimmed
    ? allTags.find((t) => t.name.toLowerCase() === lowerTrimmed)
    : null

  const handleSubmit = () => {
    if (!trimmed) return
    if (exactMatch) {
      if (!photoTagIds.has(exactMatch.id)) {
        onAttachExisting(exactMatch.id)
      }
      onTagInputChange('')
    } else {
      onCreateAndAttach(trimmed)
    }
  }

  return (
    <div className="space-y-2">
      {/* Existing tag chips */}
      {photoTags.length > 0 ? (
        <div className="flex flex-wrap gap-1">
          {photoTags.map((tag) => (
            <span
              key={tag.id}
              className="flex items-center gap-1 rounded bg-surface-2 px-2 py-0.5 text-xs text-text"
              style={tag.color ? { backgroundColor: `${tag.color}33`, color: tag.color } : undefined}
            >
              {tag.name}
              <button
                onClick={() => onRemove(tag.id)}
                className="rounded p-0.5 opacity-60 hover:bg-surface-offset hover:opacity-100"
                title="Remove tag"
              >
                <X className="h-3 w-3" />
              </button>
            </span>
          ))}
        </div>
      ) : (
        <div className="text-xs text-text-faint">No tags</div>
      )}

      {/* Add tag input + suggestions */}
      <div className="relative">
        <input
          type="text"
          value={tagInput}
          onChange={(e) => onTagInputChange(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter') {
              e.preventDefault()
              handleSubmit()
            } else if (e.key === 'Escape') {
              onTagInputChange('')
            }
          }}
          placeholder="Add tag…"
          className="w-full rounded border border-border bg-bg px-2 py-1 text-xs text-text placeholder-text-faint focus:border-primary focus:outline-none"
        />
        {suggestions.length > 0 && (
          <div className="mt-1 rounded border border-border bg-bg shadow-md">
            {suggestions.map((s) => (
              <button
                key={s.id}
                onClick={() => {
                  onAttachExisting(s.id)
                  onTagInputChange('')
                }}
                className="block w-full px-2 py-1 text-left text-xs text-text hover:bg-surface-2"
              >
                {s.name}
              </button>
            ))}
          </div>
        )}
        {trimmed && !exactMatch && (
          <button
            onClick={handleSubmit}
            className="mt-1 w-full rounded border border-dashed border-primary/50 px-2 py-1 text-left text-xs text-primary hover:bg-primary/10"
          >
            + Create "{trimmed}"
          </button>
        )}
      </div>
    </div>
  )
}

function Field({ label, value }: { label: string; value: string }) {
  return (
    <div>
      <span className="text-text-muted">{label}:</span>
      <p className="break-words text-text">{value}</p>
    </div>
  )
}
