import { zodResolver } from '@hookform/resolvers/zod'
import type { PGTrigger } from '@supabase/pg-meta'
import { Terminal } from 'lucide-react'
import { useEffect, useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
  Button,
  Checkbox,
  cn,
  Form,
  FormControl,
  FormField,
  Input,
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
  Separator,
  Sheet,
  SheetContent,
  SheetFooter,
  SheetHeader,
  SheetTitle,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import * as z from 'zod'

import ChooseFunctionForm from './ChooseFunctionForm'
import {
  TRIGGER_ENABLED_MODES,
  TRIGGER_EVENTS,
  TRIGGER_ORIENTATIONS,
  TRIGGER_TYPES,
} from './Triggers.constants'
import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
import FormBoxEmpty from '@/components/ui/FormBoxEmpty'
import { useDatabaseTriggerCreateMutation } from '@/data/database-triggers/database-trigger-create-mutation'
import { useDatabaseTriggerUpdateMutation } from '@/data/database-triggers/database-trigger-update-mutation'
import { useTablesQuery } from '@/data/tables/tables-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { useConfirmOnClose } from '@/hooks/ui/useConfirmOnClose'
import { useProtectedSchemas } from '@/hooks/useProtectedSchemas'

const formId = 'create-trigger'

const FormSchema = z.object({
  name: z
    .string()
    .min(1, 'Please provide a name for your trigger')
    .regex(/^\S+$/, 'Name should not contain spaces or whitespaces'),
  schema: z.string(),
  table: z.string(),
  activation: z.enum(['BEFORE', 'AFTER', 'INSTEAD OF']),
  enabled_mode: z.enum(['ORIGIN', 'REPLICA', 'ALWAYS', 'DISABLED']),
  orientation: z.enum(['ROW', 'STATEMENT']),
  function_name: z.string().min(1, 'Please select a database function for your trigger to call'),
  function_schema: z.string(),
  events: z.array(z.string()).min(1, 'Please select at least one event'),

  // For UI handling, not to be passed to the final request
  tableId: z.string().optional(),
})

const defaultValues: z.infer<typeof FormSchema> = {
  name: '',
  schema: '',
  table: '',
  activation: 'AFTER',
  orientation: 'ROW',
  function_name: '',
  function_schema: '',
  enabled_mode: 'ORIGIN',
  events: [],
}

interface TriggerSheetProps {
  selectedTrigger?: PGTrigger
  isDuplicatingTrigger?: boolean
  open: boolean
  onClose: () => void
}

export const TriggerSheet = ({
  selectedTrigger,
  isDuplicatingTrigger,
  open,
  onClose,
}: TriggerSheetProps) => {
  const { data: project } = useSelectedProjectQuery()

  const [showFunctionSelector, setShowFunctionSelector] = useState(false)

  const { mutate: createDatabaseTrigger, isPending: isCreating } = useDatabaseTriggerCreateMutation(
    {
      onSuccess: () => {
        toast.success(`Successfully created trigger`)
        onClose()
      },
      onError: (error) => {
        toast.error(`Failed to create trigger: ${error.message}`)
      },
    }
  )
  const { mutate: updateDatabaseTrigger, isPending: isUpdating } = useDatabaseTriggerUpdateMutation(
    {
      onSuccess: () => {
        toast.success(`Successfully updated trigger`)
        onClose()
      },
      onError: (error) => {
        toast.error(`Failed to update trigger: ${error.message}`)
      },
    }
  )

  const { data = [], isSuccess: isSuccessTables } = useTablesQuery({
    projectRef: project?.ref,
    connectionString: project?.connectionString,
  })
  const { data: protectedSchemas, isSuccess: isSuccessProtectedSchemas } = useProtectedSchemas()
  const isSuccess = isSuccessTables && isSuccessProtectedSchemas

  const tables = data
    .sort((a, b) => a.schema.localeCompare(b.schema))
    .filter((a) => !protectedSchemas.find((s) => s.name === a.schema))
  const isEditing = !isDuplicatingTrigger && !!selectedTrigger

  const form = useForm<z.infer<typeof FormSchema>>({
    mode: 'onSubmit',
    reValidateMode: 'onSubmit',
    resolver: zodResolver(FormSchema as any),
    defaultValues,
  })
  const { function_name, function_schema } = form.watch()

  const { confirmOnClose, handleOpenChange, modalProps } = useConfirmOnClose({
    checkIsDirty: () => form.formState.isDirty,
    onClose,
  })

  const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (values) => {
    if (!project) return console.error('Project is required')
    const { tableId, ...payload } = values

    if (isEditing) {
      updateDatabaseTrigger({
        projectRef: project?.ref,
        connectionString: project?.connectionString,
        originalTrigger: selectedTrigger,
        payload: { name: payload.name, enabled_mode: payload.enabled_mode },
      })
    } else {
      createDatabaseTrigger({
        projectRef: project?.ref,
        connectionString: project?.connectionString,
        payload,
      })
    }
  }

  useEffect(() => {
    if (open && isSuccess) {
      form.clearErrors()

      if (isDuplicatingTrigger && selectedTrigger) {
        const initalSelectedTable = tables.find((t) => t.name === selectedTrigger.table)

        form.reset({
          ...selectedTrigger,
          tableId: initalSelectedTable?.id.toString(),
          table: initalSelectedTable?.name,
          schema: initalSelectedTable?.schema,
        })
      } else if (isEditing) {
        form.reset(selectedTrigger)
      } else if (tables.length > 0) {
        form.reset({
          ...defaultValues,
          tableId: tables[0].id.toString(),
          table: tables[0].name,
          schema: tables[0].schema,
        })
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, isSuccess])

  return (
    <>
      <Sheet open={open} onOpenChange={handleOpenChange}>
        <SheetContent size="lg" className="flex flex-col gap-0">
          <SheetHeader>
            <SheetTitle>
              {isDuplicatingTrigger
                ? 'Duplicate trigger'
                : isEditing
                  ? `Edit database trigger: ${selectedTrigger.name}`
                  : 'Create a new database trigger'}
            </SheetTitle>
          </SheetHeader>

          <Form {...form}>
            <form
              id={formId}
              className="flex-1 flex flex-col gap-y-6 overflow-auto py-6"
              onSubmit={form.handleSubmit(onSubmit)}
            >
              <FormField
                name="name"
                control={form.control}
                render={({ field }) => (
                  <FormItemLayout
                    className="px-5"
                    layout="horizontal"
                    label="Name of trigger"
                    description="Do not use spaces/whitespace."
                  >
                    <FormControl>
                      <Input {...field} placeholder="Name of trigger" />
                    </FormControl>
                  </FormItemLayout>
                )}
              />

              {isEditing ? (
                <FormField
                  name="enabled_mode"
                  control={form.control}
                  render={({ field }) => (
                    <FormItemLayout
                      className="px-5"
                      layout="horizontal"
                      label="Enabled mode"
                      description="Determines if a trigger should or should not fire. Can also be used to disable a trigger, but not delete it."
                    >
                      <FormControl>
                        <Select defaultValue={field.value} onValueChange={field.onChange}>
                          <SelectTrigger className="col-span-8">
                            {
                              TRIGGER_ENABLED_MODES.find((option) => option.value === field.value)
                                ?.label
                            }
                          </SelectTrigger>
                          <SelectContent>
                            {TRIGGER_ENABLED_MODES.map((option) => (
                              <SelectItem key={option.value} value={option.value}>
                                <p className="text-foreground">{option.label}</p>
                                <p className="text-foreground-lighter">{option.description}</p>
                              </SelectItem>
                            ))}
                          </SelectContent>
                        </Select>
                      </FormControl>
                    </FormItemLayout>
                  )}
                />
              ) : (
                <>
                  <Separator />

                  <FormField
                    name="tableId"
                    control={form.control}
                    render={({ field }) => (
                      <FormItemLayout
                        className="px-5"
                        layout="horizontal"
                        label="Table"
                        description="Trigger will watch for changes on this table"
                      >
                        <FormControl>
                          <Select
                            defaultValue={field.value}
                            onValueChange={(val) => {
                              // mark table ID as dirty to trigger validation
                              field.onChange(val)
                              const table = tables.find((x) => x.id.toString() === val)
                              if (table) {
                                form.setValue('table', table.name, { shouldDirty: true })
                                form.setValue('schema', table.schema, { shouldDirty: true })
                              }
                            }}
                          >
                            <SelectTrigger className="col-span-8">
                              <SelectValue />
                            </SelectTrigger>
                            <SelectContent>
                              {tables.map((table) => (
                                <SelectItem key={table.id} value={table.id.toString()}>
                                  <span className="text-foreground-light">{table.schema}.</span>
                                  <span className="text-foreground">{table.name}</span>
                                </SelectItem>
                              ))}
                            </SelectContent>
                          </Select>
                        </FormControl>
                      </FormItemLayout>
                    )}
                  />

                  <FormField
                    name="events"
                    control={form.control}
                    render={() => (
                      <FormItemLayout
                        className="px-5"
                        layout="horizontal"
                        label="Events"
                        description="These are the events that are watched by the trigger, only the events selected above will fire the trigger on the table you've selected."
                      >
                        {TRIGGER_EVENTS.map((event) => (
                          <FormField
                            key={event.value}
                            control={form.control}
                            name="events"
                            render={({ field }) => (
                              <FormItemLayout
                                hideMessage
                                layout="flex"
                                label={event.label}
                                description={event.description}
                              >
                                <FormControl>
                                  <Checkbox
                                    className="translate-y-[2px]"
                                    checked={field.value?.includes(event.value)}
                                    onCheckedChange={(checked) => {
                                      return checked
                                        ? field.onChange([...field.value, event.value])
                                        : field.onChange(
                                            field.value?.filter((value) => value !== event.value)
                                          )
                                    }}
                                  />
                                </FormControl>
                              </FormItemLayout>
                            )}
                          />
                        ))}
                      </FormItemLayout>
                    )}
                  />

                  <FormField
                    name="activation"
                    control={form.control}
                    render={({ field }) => (
                      <FormItemLayout
                        className="px-5"
                        layout="horizontal"
                        label="Trigger type"
                        description="Determines when your trigger fires"
                      >
                        <FormControl>
                          <Select defaultValue={field.value} onValueChange={field.onChange}>
                            <SelectTrigger className="col-span-8">
                              {TRIGGER_TYPES.find((option) => option.value === field.value)?.label}
                            </SelectTrigger>
                            <SelectContent>
                              {TRIGGER_TYPES.map((option) => (
                                <SelectItem key={option.value} value={option.value}>
                                  <p className="text-foreground">{option.label}</p>
                                  <p className="text-foreground-lighter">{option.description}</p>
                                </SelectItem>
                              ))}
                            </SelectContent>
                          </Select>
                        </FormControl>
                      </FormItemLayout>
                    )}
                  />

                  <FormField
                    name="orientation"
                    control={form.control}
                    render={({ field }) => (
                      <FormItemLayout
                        className="px-5"
                        layout="horizontal"
                        label="Orientation"
                        description="Identifies whether the trigger fires once for each processed row or once for each statement"
                      >
                        <FormControl>
                          <Select defaultValue={field.value} onValueChange={field.onChange}>
                            <SelectTrigger className="col-span-8">
                              {
                                TRIGGER_ORIENTATIONS.find((option) => option.value === field.value)
                                  ?.label
                              }
                            </SelectTrigger>
                            <SelectContent>
                              {TRIGGER_ORIENTATIONS.map((option) => (
                                <SelectItem key={option.value} value={option.value}>
                                  <p className="text-foreground">{option.label}</p>
                                  <p className="text-foreground-lighter">{option.description}</p>
                                </SelectItem>
                              ))}
                            </SelectContent>
                          </Select>
                        </FormControl>
                      </FormItemLayout>
                    )}
                  />

                  <Separator />

                  <FormField
                    name="function_name"
                    control={form.control}
                    render={() => (
                      <FormItemLayout layout="vertical" className="px-5">
                        <FormControl>
                          <div className="flex flex-col gap-y-2">
                            <p className="text-sm">Function to trigger</p>
                            {function_name.length === 0 ? (
                              <button
                                type="button"
                                className={cn(
                                  'relative w-full rounded-sm border border-default',
                                  'bg-surface-200 px-5 py-1 shadow-xs transition-all',
                                  'hover:border-strong hover:bg-overlay-hover'
                                )}
                                onClick={() => setShowFunctionSelector(true)}
                              >
                                <FormBoxEmpty
                                  icon={<Terminal size={14} strokeWidth={2} />}
                                  text="Choose a function to trigger"
                                />
                              </button>
                            ) : (
                              <div
                                className={cn(
                                  'relative w-full flex items-center justify-between',
                                  'space-x-3 px-5 py-4 border border-default',
                                  'rounded-sm shadow-xs transition-shadow'
                                )}
                              >
                                <div className="flex items-center gap-2">
                                  <div className="flex h-6 w-6 items-center justify-center rounded-sm bg-foreground text-background focus-within:bg-foreground/10">
                                    <Terminal size="18" strokeWidth={2} width={14} />
                                  </div>
                                  <p>
                                    <span className="text-sm text-foreground-light">
                                      {function_schema}
                                    </span>
                                    .
                                    <span className="text-sm text-foreground">{function_name}</span>
                                  </p>
                                </div>
                                <Button
                                  type="default"
                                  onClick={() => setShowFunctionSelector(true)}
                                >
                                  Change function
                                </Button>
                              </div>
                            )}
                          </div>
                        </FormControl>
                      </FormItemLayout>
                    )}
                  />
                </>
              )}
            </form>
          </Form>

          <SheetFooter className="shrink-0">
            <Button
              type="default"
              htmlType="reset"
              disabled={isCreating || isUpdating}
              onClick={confirmOnClose}
            >
              Cancel
            </Button>
            <Button form={formId} htmlType="submit" loading={isCreating || isUpdating}>
              {isEditing ? 'Save' : 'Create'} trigger
            </Button>
          </SheetFooter>

          <DiscardChangesConfirmationDialog {...modalProps} />
        </SheetContent>
      </Sheet>

      <ChooseFunctionForm
        visible={showFunctionSelector}
        setVisible={setShowFunctionSelector}
        onChange={(fn) => {
          form.setValue('function_name', fn.name, { shouldDirty: true })
          form.setValue('function_schema', fn.schema, { shouldDirty: true })
        }}
      />
    </>
  )
}
