"use client"

import { useCallback } from "react"
import { useQueryClient } from "@tanstack/react-query"
import { toast } from "sonner"
import { useUpdateContact } from "@/lib/api/hooks/use-contact-mutations"
import { buildContactUpdatePayload } from "@/lib/api/adapters"
import {
  findApiContactInCaches,
  replaceContactInBookCaches,
  resolveContactForUpdate,
} from "@/lib/api/contact-book-cache"
import { fetchContactByPath } from "@/lib/api/hooks/use-contact-queries"
import { contactApiPath } from "@/lib/contacts/contact-api-path"
import type { FullContact } from "@/lib/contacts/types"
import type { ContactBulkEditField } from "@/lib/contacts/bulk-edit-fields"
import { applyBulkFieldValue } from "@/lib/contacts/bulk-edit-fields"
import { useBlockedSendersStore } from "@/lib/stores/blocked-senders-store"
import { useNavStore } from "@/lib/stores/nav-store"
import type { ContactLabelPresence } from "./contact-label-picker-block"

function contactHasLabel(contact: FullContact, labelId: string): boolean {
  return contact.labels?.includes(labelId) ?? false
}

function mergeLabelOnContact(
  contact: FullContact,
  labelId: string,
  add: boolean,
): FullContact {
  const current = contact.labels ?? []
  const next = add
    ? current.includes(labelId)
      ? current
      : [...current, labelId]
    : current.filter((id) => id !== labelId)
  return {
    ...contact,
    labels: next.length ? next : undefined,
    updatedAt: Date.now(),
  }
}

export function useContactBulkActions() {
  const queryClient = useQueryClient()
  const updateContact = useUpdateContact()
  const blockSenders = useBlockedSendersStore((s) => s.blockSenders)
  const labelRows = useNavStore((s) => s.labelRows)
  const addLabelRowFromSidebar = useNavStore((s) => s.addLabelRowFromSidebar)

  const resolveContactEtag = useCallback(
    async (contact: FullContact): Promise<FullContact | null> => {
      let resolved = resolveContactForUpdate(queryClient, contact)
      if (resolved.etag) return resolved

      const apiPath = contactApiPath(resolved)
      try {
        const fetched = await fetchContactByPath(apiPath)
        replaceContactInBookCaches(queryClient, apiPath, fetched)
        resolved = resolveContactForUpdate(queryClient, {
          ...resolved,
          etag: fetched.etag,
          path: fetched.path ?? resolved.path,
        })
      } catch {
        return null
      }

      return resolved.etag ? resolved : null
    },
    [queryClient],
  )

  const persistContact = useCallback(
    async (
      contact: FullContact,
      opts?: {
        bulkField?: ContactBulkEditField
        bulkValue?: string
        patchLabels?: boolean
        skipInvalidation?: boolean
      },
    ) => {
      const resolved = await resolveContactEtag(contact)
      if (!resolved?.etag) return false

      const existing = findApiContactInCaches(queryClient, resolved)
      const payload = buildContactUpdatePayload(existing, resolved, {
        bulkField: opts?.bulkField,
        bulkValue: opts?.bulkValue,
        patchLabels: opts?.patchLabels,
      })
      await updateContact.mutateAsync({
        path: contactApiPath(resolved),
        etag: resolved.etag,
        contact: payload,
        skipInvalidation: opts?.skipInvalidation,
      })
      return true
    },
    [queryClient, resolveContactEtag, updateContact],
  )

  const getLabelPresence = useCallback(
    (contacts: FullContact[], labelId: string): ContactLabelPresence => {
      if (contacts.length === 0) return "none"
      let n = 0
      for (const contact of contacts) {
        if (contactHasLabel(contact, labelId)) n++
      }
      if (n === 0) return "none"
      if (n === contacts.length) return "all"
      return "some"
    },
    [],
  )

  const runSequentialUpdates = useCallback(
    async (
      contacts: FullContact[],
      buildNext: (contact: FullContact) => FullContact,
      opts?: {
        bulkField?: ContactBulkEditField
        bulkValue?: string
        patchLabels?: boolean
      },
    ) => {
      let skipped = 0
      let failed = 0
      let updated = 0

      for (const contact of contacts) {
        const next = buildNext(resolveContactForUpdate(queryClient, contact))
        try {
          const ok = await persistContact(next, {
            ...opts,
            skipInvalidation: true,
          })
          if (ok) updated++
          else skipped++
        } catch {
          failed++
        }
      }

      await queryClient.invalidateQueries({ queryKey: ["contacts"] })

      return { skipped, failed, updated }
    },
    [persistContact, queryClient],
  )

  const toggleLabelOnContacts = useCallback(
    async (contacts: FullContact[], labelId: string) => {
      if (contacts.length === 0) return
      const presence = getLabelPresence(contacts, labelId)
      const shouldAdd = presence !== "all"

      const targets = contacts.filter((contact) => {
        const has = contactHasLabel(contact, labelId)
        return shouldAdd ? !has : has
      })

      const { skipped, failed, updated } = await runSequentialUpdates(
        targets,
        (contact) => mergeLabelOnContact(contact, labelId, shouldAdd),
        { patchLabels: true },
      )

      if (updated === 0 && skipped === 0 && failed === 0) return
      if (failed > 0) {
        toast.error(
          `${failed} mise${failed > 1 ? "s" : ""} à jour échouée${failed > 1 ? "s" : ""}`,
        )
      }
      if (skipped > 0) {
        toast.warning(
          `${skipped} contact${skipped > 1 ? "s" : ""} ignoré${skipped > 1 ? "s" : ""} (version inconnue)`,
        )
      }
    },
    [getLabelPresence, runSequentialUpdates],
  )

  const createAndApplyLabel = useCallback(
    async (contacts: FullContact[], labelText: string) => {
      addLabelRowFromSidebar(labelText)
      const row = useNavStore
        .getState()
        .labelRows.find(
          (r) => r.label.toLowerCase() === labelText.trim().toLowerCase(),
        )
      if (row) await toggleLabelOnContacts(contacts, row.id)
    },
    [addLabelRowFromSidebar, toggleLabelOnContacts],
  )

  const blockContacts = useCallback(
    (contacts: FullContact[]) => {
      const emails = contacts
        .flatMap((c) => c.emails.map((e) => e.value))
        .filter(Boolean)
      if (emails.length === 0) {
        toast.error("Aucune adresse e-mail à bloquer dans la sélection")
        return
      }
      blockSenders(emails)
      toast.success(
        `${emails.length} adresse${emails.length > 1 ? "s" : ""} bloquée${emails.length > 1 ? "s" : ""}`,
      )
    },
    [blockSenders],
  )

  const applyBulkField = useCallback(
    async (contacts: FullContact[], field: ContactBulkEditField, value: string) => {
      const toUpdate = contacts.map((c) => applyBulkFieldValue(c, field, value))
      const { skipped, failed, updated } = await runSequentialUpdates(
        toUpdate,
        (contact) => contact,
        { bulkField: field, bulkValue: value },
      )

      if (updated > 0) {
        toast.success(
          `${updated} contact${updated > 1 ? "s" : ""} mis à jour`,
        )
      }
      if (failed > 0) {
        toast.error(
          `${failed} mise${failed > 1 ? "s" : ""} à jour échouée${failed > 1 ? "s" : ""}`,
        )
      }
      if (skipped > 0) {
        toast.warning(
          `${skipped} contact${skipped > 1 ? "s" : ""} ignoré${skipped > 1 ? "s" : ""} (version inconnue)`,
        )
      }
    },
    [runSequentialUpdates],
  )

  const resolveLabelVisualById = useCallback(
    (labelId: string) => {
      const row = labelRows.find((r) => r.id === labelId)
      if (row?.icon) return { kind: "iconify" as const, icon: row.icon }
      if (row?.color) return { kind: "dot" as const, colorClass: row.color }
      return { kind: "dot" as const, colorClass: "bg-gray-400" }
    },
    [labelRows],
  )

  return {
    getLabelPresence,
    toggleLabelOnContacts,
    createAndApplyLabel,
    blockContacts,
    applyBulkField,
    resolveLabelVisualById,
    isUpdating: updateContact.isPending,
  }
}
