'use client'

import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useDemoContacts } from '@/lib/demo/demo-contacts-context'
import { DEMO_CONTACTS_QUERY_ROOT } from '@/lib/demo/demo-contacts-bootstrap'
import { useDemoContactsStore } from '@/lib/demo/demo-contacts-store'
import { apiClient, OfflineError } from '../client'
import { enqueue } from '../offline-queue'
import { fullContactToApiContact, buildContactUpdatePayload } from '../adapters'
import {
  appendContactToBookCache,
  replaceContactInBookCaches,
} from '../contact-book-cache'
import { invalidateContactListCache } from '../contact-list-cache'
import { contactApiPath } from '@/lib/contacts/contact-api-path'
import { mergeTwoContacts, mergeManyContacts } from '@/lib/contacts/merge-contacts'
import type { FullContact } from '@/lib/contacts/types'
import type { ApiContact } from '../types'

export function useCreateContact() {
  const queryClient = useQueryClient()
  const demoContacts = useDemoContacts()
  const isDemoContacts = demoContacts?.enabled ?? false

  return useMutation({
    mutationFn: async (vars: { bookId: string; contact: Partial<ApiContact> }) => {
      if (isDemoContacts) {
        return useDemoContactsStore
          .getState()
          .createContact(vars.bookId, vars.contact)
      }
      const created = await apiClient.post<ApiContact | undefined>(
        `/contacts/books/${vars.bookId}`,
        vars.contact,
      )
      if (created?.uid) return created
      return vars.contact as ApiContact
    },
    onSuccess: (data, vars) => {
      if (isDemoContacts) {
        useDemoContactsStore.getState().bump()
        void queryClient.invalidateQueries({ queryKey: DEMO_CONTACTS_QUERY_ROOT })
        demoContacts?.notify('Contact créé')
        return
      }
      const contact = data?.uid ? data : (vars.contact as ApiContact)
      if (contact?.uid) {
        appendContactToBookCache(queryClient, vars.bookId, {
          ...contact,
          etag: contact.etag ?? data?.etag,
          path: contact.path ?? data?.path,
        })
      }
      invalidateContactListCache(vars.bookId)
      queryClient.invalidateQueries({ queryKey: ['contacts', vars.bookId] })
    },
    onError: (err, vars) => {
      if (err instanceof OfflineError) {
        enqueue({
          id: crypto.randomUUID(),
          timestamp: Date.now(),
          type: 'create_contact',
          payload: { bookId: vars.bookId, ...vars.contact },
          retries: 0,
        })
      }
    },
  })
}

export function useUpdateContact() {
  const queryClient = useQueryClient()
  const demoContacts = useDemoContacts()
  const isDemoContacts = demoContacts?.enabled ?? false

  return useMutation({
    mutationFn: async (vars: {
      path: string
      contact: Partial<ApiContact>
      etag?: string
      skipInvalidation?: boolean
    }) => {
      if (isDemoContacts) {
        useDemoContactsStore.getState().updateContact(vars.path, vars.contact)
        return { etag: `"demo-v2"` }
      }
      const ifMatch = vars.etag ?? vars.contact.etag
      const headers: Record<string, string> = {}
      if (ifMatch) {
        headers['If-Match'] = ifMatch
      }
      const { etag: _bodyEtag, ...contactBody } = vars.contact
      const apiPath = vars.path.replace(/^\/+/, '')
      return apiClient.put<{ etag?: string }>(`/contacts/${apiPath}`, contactBody, headers)
    },
    onSuccess: (data, vars) => {
      if (isDemoContacts) {
        useDemoContactsStore.getState().bump()
        void queryClient.invalidateQueries({ queryKey: DEMO_CONTACTS_QUERY_ROOT })
        return
      }
      const apiPath = vars.path.replace(/^\/+/, '')
      const nextEtag = data?.etag ?? vars.etag
      replaceContactInBookCaches(queryClient, apiPath, {
        ...vars.contact,
        etag: nextEtag,
      })
      invalidateContactListCache()
      if (!vars.skipInvalidation) {
        queryClient.invalidateQueries({ queryKey: ['contacts'] })
      }
    },
    onError: (err, vars) => {
      if (err instanceof OfflineError) {
        enqueue({
          id: crypto.randomUUID(),
          timestamp: Date.now(),
          type: 'update_contact',
          payload: { path: vars.path, ...vars.contact },
          retries: 0,
        })
      }
    },
  })
}

export function useDeleteContact() {
  const queryClient = useQueryClient()
  const demoContacts = useDemoContacts()
  const isDemoContacts = demoContacts?.enabled ?? false

  return useMutation({
    mutationFn: async (vars: { path: string; bookId?: string }) => {
      if (isDemoContacts) {
        useDemoContactsStore.getState().deleteContact(vars.path)
        return
      }
      return apiClient.delete(`/contacts/${vars.path}`)
    },
    onMutate: async (vars) => {
      await queryClient.cancelQueries({ queryKey: ['contacts'] })
      const queries = queryClient.getQueriesData<ApiContact[]>({ queryKey: ['contacts'] })
      const snapshots: [readonly unknown[], ApiContact[] | undefined][] = []

      for (const [key, data] of queries) {
        if (data) {
          snapshots.push([key, data])
          queryClient.setQueryData(
            key,
            data.filter((c) => c.path !== vars.path && c.uid !== vars.path)
          )
        }
      }
      return { snapshots }
    },
    onError: (err, vars, context) => {
      if (context?.snapshots) {
        for (const [key, data] of context.snapshots) {
          queryClient.setQueryData(key, data)
        }
      }
      if (err instanceof OfflineError) {
        enqueue({
          id: crypto.randomUUID(),
          timestamp: Date.now(),
          type: 'delete_contact',
          payload: { path: vars.path, uid: vars.path },
          retries: 0,
        })
      }
    },
    onSettled: () => {
      if (isDemoContacts) {
        useDemoContactsStore.getState().bump()
        void queryClient.invalidateQueries({ queryKey: DEMO_CONTACTS_QUERY_ROOT })
        demoContacts?.notify('Contact supprimé')
        return
      }
      invalidateContactListCache()
      queryClient.invalidateQueries({ queryKey: ['contacts'] })
    },
  })
}

export function useMergeDuplicates() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async (vars: { bookId: string }) =>
      apiClient.post(`/contacts/books/${vars.bookId}/merge-duplicates`),
    onSuccess: (_data, vars) => {
      invalidateContactListCache(vars.bookId)
      queryClient.invalidateQueries({ queryKey: ['contacts', vars.bookId] })
    },
  })
}

export function useMergeContactPair() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async (vars: {
      bookId: string
      contactA: FullContact
      contactB: FullContact
    }) => {
      const { primary, secondary, merged } = mergeTwoContacts(vars.contactA, vars.contactB)
      const primaryPath = contactApiPath(primary)
      const secondaryPath = contactApiPath(secondary)

      const headers: Record<string, string> = {}
      if (primary.etag) {
        headers['If-Match'] = primary.etag
      }

      await apiClient.put(
        `/contacts/${primaryPath}`,
        fullContactToApiContact(merged),
        headers,
      )

      if (secondaryPath !== primaryPath) {
        await apiClient.delete(`/contacts/${secondaryPath}`)
      }
    },
    onSuccess: (_data, vars) => {
      invalidateContactListCache(vars.bookId)
      queryClient.invalidateQueries({ queryKey: ['contacts', vars.bookId] })
    },
  })
}

export function useMergeManyContacts() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async (vars: {
      bookId: string
      contacts: FullContact[]
      primaryId?: string
    }) => {
      const result = mergeManyContacts(vars.contacts, vars.primaryId)
      if (!result) {
        throw new Error('At least 2 contacts are required to merge')
      }
      const { primary, secondaries, merged } = result
      if (!primary.etag) {
        throw new Error('Cannot merge: unknown contact version. Reload the list.')
      }

      const headers: Record<string, string> = {
        'If-Match': primary.etag,
      }

      await apiClient.put(
        `/contacts/${contactApiPath(primary)}`,
        fullContactToApiContact(merged),
        headers,
      )

      for (const secondary of secondaries) {
        const secondaryPath = contactApiPath(secondary)
        if (secondaryPath !== contactApiPath(primary)) {
          await apiClient.delete(`/contacts/${secondaryPath}`)
        }
      }
    },
    onSuccess: (_data, vars) => {
      invalidateContactListCache(vars.bookId)
      queryClient.invalidateQueries({ queryKey: ['contacts', vars.bookId] })
    },
  })
}
