import { zodResolver } from '@hookform/resolvers/zod'
import { ChevronDown } from 'lucide-react'
import { useEffect, useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
  Button,
  Checkbox,
  cn,
  Form,
  FormControl,
  FormField,
  Input,
  Label,
  Separator,
  Sheet,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetSection,
  SheetTitle,
  Switch,
  Textarea,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { KeyValueFieldArray } from 'ui-patterns/form/KeyValueFieldArray/KeyValueFieldArray'
import {
  getKeyValueFieldArrayValidationIssues,
  stripEmptyKeyValueFieldArrayRows,
} from 'ui-patterns/form/KeyValueFieldArray/validation'
import * as z from 'zod'

import type {
  UpsertWebhookEndpointInput,
  WebhookEndpoint,
  WebhookScope,
} from './PlatformWebhooks.types'
import { generateWebhookEndpointName } from './PlatformWebhooks.utils'
import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
import { InlineLink } from '@/components/ui/InlineLink'
import { useConfirmOnClose } from '@/hooks/ui/useConfirmOnClose'
import { httpEndpointUrlSchema } from '@/lib/validation/http-url'

const endpointFormSchema = z
  .object({
    name: z.string().trim().max(64, 'Name cannot exceed 64 characters'),
    url: httpEndpointUrlSchema({
      requiredMessage: 'Please provide a URL',
      invalidMessage: 'Please provide a valid URL',
      prefixMessage: 'Please prefix your URL with http:// or https://',
    }),
    description: z.string().trim().max(512, 'Description cannot exceed 512 characters'),
    enabled: z.boolean().default(true),
    subscribeAll: z.boolean().default(false),
    eventTypes: z.array(z.string()).default([]),
    customHeaders: z
      .array(
        z.object({
          key: z.string().trim(),
          value: z.string().trim(),
        })
      )
      .default([]),
  })
  .superRefine((data, ctx) => {
    if (!data.subscribeAll && data.eventTypes.length === 0) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: 'Select at least one event type',
        path: ['eventTypes'],
      })
    }

    getKeyValueFieldArrayValidationIssues({
      rows: data.customHeaders,
      keyFieldName: 'key',
      valueFieldName: 'value',
      keyRequiredMessage: 'Header name is required',
      valueRequiredMessage: 'Header value is required',
    }).forEach((issue) => {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: issue.message,
        path: ['customHeaders', ...issue.path],
      })
    })
  })

export type EndpointFormValues = z.infer<typeof endpointFormSchema>

const toEventTypes = (values: EndpointFormValues) =>
  values.subscribeAll ? ['*'] : values.eventTypes

type EventTypeGroup = {
  id: string
  label: string
  eventTypes: string[]
}

const buildEventTypeGroups = (scope: WebhookScope, eventTypes: string[]): EventTypeGroup[] => {
  if (scope === 'project') {
    return [{ id: 'project', label: 'Project events', eventTypes }]
  }

  const organizationEvents = eventTypes.filter((eventType) => eventType.startsWith('organization.'))
  const projectEvents = eventTypes.filter((eventType) => eventType.startsWith('project.'))
  const ungroupedEvents = eventTypes.filter(
    (eventType) => !eventType.startsWith('organization.') && !eventType.startsWith('project.')
  )

  return [
    { id: 'organization', label: 'Organization events', eventTypes: organizationEvents },
    { id: 'project', label: 'Project events', eventTypes: projectEvents },
    { id: 'other', label: 'Other events', eventTypes: ungroupedEvents },
  ].filter((group) => group.eventTypes.length > 0)
}

const toggleEventType = (selectedEventTypes: string[], eventType: string, checked: boolean) => {
  if (checked) return [...new Set([...selectedEventTypes, eventType])]
  return selectedEventTypes.filter((value) => value !== eventType)
}

const toggleEventTypeGroup = (
  selectedEventTypes: string[],
  groupedEventTypes: string[],
  checked: boolean
) => {
  if (checked) return [...new Set([...selectedEventTypes, ...groupedEventTypes])]
  return selectedEventTypes.filter((value) => !groupedEventTypes.includes(value))
}

const toControlId = (prefix: string, value: string) =>
  `${prefix}-${value.replace(/[^a-zA-Z0-9_-]/g, '-')}`

export const toEndpointPayload = (values: EndpointFormValues): UpsertWebhookEndpointInput => ({
  name: values.name,
  url: values.url,
  description: values.description,
  enabled: values.enabled,
  eventTypes: toEventTypes(values),
  customHeaders: stripEmptyKeyValueFieldArrayRows({
    rows: values.customHeaders,
    keyFieldName: 'key',
    valueFieldName: 'value',
  }),
})

interface EndpointSheetProps {
  visible: boolean
  mode: 'create' | 'edit'
  scope: WebhookScope
  orgSlug?: string
  endpoint?: WebhookEndpoint
  enabledOverride?: boolean | null
  eventTypes: string[]
  onClose: () => void
  onSubmit: (values: EndpointFormValues) => void
}

export const PlatformWebhooksEndpointSheet = ({
  visible,
  mode,
  scope,
  orgSlug,
  endpoint,
  enabledOverride,
  eventTypes,
  onClose,
  onSubmit,
}: EndpointSheetProps) => {
  const form = useForm<EndpointFormValues>({
    resolver: zodResolver(endpointFormSchema as any),
    defaultValues: {
      name: generateWebhookEndpointName(),
      url: '',
      description: '',
      enabled: true,
      subscribeAll: false,
      eventTypes: [],
      customHeaders: [],
    },
  })
  const isDirty = form.formState.isDirty
  const {
    confirmOnClose,
    handleOpenChange,
    modalProps: discardChangesModalProps,
  } = useConfirmOnClose({
    checkIsDirty: () => isDirty,
    onClose,
  })

  const subscribeAll = form.watch('subscribeAll')
  const selectedEventTypes = form.watch('eventTypes')
  const groupedEventTypes = useMemo(
    () => buildEventTypeGroups(scope, eventTypes),
    [scope, eventTypes]
  )
  const [openEventGroups, setOpenEventGroups] = useState<string[]>([])

  useEffect(() => {
    if (!visible) return

    if (!endpoint) {
      form.reset({
        name: generateWebhookEndpointName(),
        url: '',
        description: '',
        enabled: true,
        subscribeAll: false,
        eventTypes: [],
        customHeaders: [],
      })
      return
    }

    form.reset({
      name: endpoint.name,
      url: endpoint.url,
      description: endpoint.description,
      enabled: enabledOverride ?? endpoint.enabled,
      subscribeAll: endpoint.eventTypes.includes('*'),
      eventTypes: endpoint.eventTypes.includes('*') ? eventTypes : endpoint.eventTypes,
      customHeaders: endpoint.customHeaders.map((header) => ({
        key: header.key,
        value: header.value,
      })),
    })
  }, [enabledOverride, endpoint, eventTypes, form, visible])

  useEffect(() => {
    if (!visible) return
    setOpenEventGroups(groupedEventTypes.map((group) => group.id))
  }, [groupedEventTypes, visible])

  useEffect(() => {
    if (!visible) return
    const allSelected =
      eventTypes.length > 0 &&
      eventTypes.every((eventType) => selectedEventTypes.includes(eventType))

    if (subscribeAll !== allSelected) {
      form.setValue('subscribeAll', allSelected, {
        shouldDirty: true,
        shouldValidate: true,
      })
    }
  }, [eventTypes, form, selectedEventTypes, subscribeAll, visible])

  return (
    <Sheet open={visible} onOpenChange={handleOpenChange}>
      <SheetContent showClose={false} size="default" className="flex flex-col gap-0">
        <SheetHeader>
          <SheetTitle>{mode === 'create' ? 'Create endpoint' : 'Edit endpoint'}</SheetTitle>
          <SheetDescription className="sr-only">
            {mode === 'create'
              ? 'Create a webhook endpoint by setting a name, URL, and event subscriptions.'
              : 'Edit this webhook endpoint name, URL, and event subscriptions.'}
          </SheetDescription>
        </SheetHeader>
        <Separator />
        <SheetSection className="overflow-auto grow px-0 py-0">
          <Form {...form}>
            <form
              id="platform-webhook-endpoint-form"
              className="space-y-5 py-5"
              onSubmit={form.handleSubmit(onSubmit)}
            >
              <div className="px-5 space-y-5">
                <FormField
                  control={form.control}
                  name="name"
                  render={({ field }) => (
                    <FormItemLayout
                      label={
                        <>
                          Name
                          {/* Technically optional but encourage, so no (optional) label */}
                        </>
                      }
                      layout="vertical"
                      className="gap-1"
                    >
                      <FormControl>
                        <Input {...field} placeholder="winged-envelope" maxLength={64} />
                      </FormControl>
                    </FormItemLayout>
                  )}
                />

                <FormField
                  control={form.control}
                  name="url"
                  render={({ field }) => (
                    <FormItemLayout label="Endpoint URL" layout="vertical" className="gap-1">
                      <FormControl>
                        <Input {...field} placeholder="https://api.example.com/webhooks/briven" />
                      </FormControl>
                    </FormItemLayout>
                  )}
                />

                <FormField
                  control={form.control}
                  name="description"
                  render={({ field }) => (
                    <FormItemLayout
                      label={
                        <>
                          Description <span className="text-foreground-muted">(optional)</span>
                        </>
                      }
                      layout="vertical"
                      className="gap-1"
                    >
                      <FormControl>
                        <Textarea
                          {...field}
                          rows={4}
                          placeholder="Optional description for this endpoint"
                          className="resize-none"
                        />
                      </FormControl>
                    </FormItemLayout>
                  )}
                />

                {mode === 'edit' && (
                  <FormField
                    control={form.control}
                    name="enabled"
                    render={({ field }) => {
                      const enabledId = 'enabled-endpoint'
                      return (
                        <div className="rounded-md border bg-surface-100">
                          <Label
                            htmlFor={enabledId}
                            className="flex w-full cursor-pointer items-center justify-between gap-3 px-4 py-3"
                          >
                            <div className="space-y-0.5">
                              <p className="text-sm text-foreground">Enable endpoint</p>
                              <p className="text-sm text-foreground-lighter">
                                Disabled endpoints won’t receive deliveries
                              </p>
                            </div>
                            <FormControl>
                              <Switch
                                id={enabledId}
                                checked={field.value}
                                onCheckedChange={field.onChange}
                              />
                            </FormControl>
                          </Label>
                        </div>
                      )
                    }}
                  />
                )}
              </div>

              <Separator />

              <div className="px-5 space-y-3">
                <FormField
                  control={form.control}
                  name="eventTypes"
                  render={({ field, fieldState }) => {
                    const selectedTypes = field.value ?? []
                    const hasEventTypeError = !!fieldState.error

                    return (
                      <FormItemLayout
                        label="Event types"
                        description={
                          scope === 'organization' ? (
                            <>
                              Project events are triggered when any project in this organization
                              matches the event type. Add a{' '}
                              <InlineLink href="/project/_/settings/webhooks">
                                project endpoint
                              </InlineLink>{' '}
                              to listen to events on an individual project only.
                            </>
                          ) : (
                            <>
                              Project events are triggered for this project only. Add an{' '}
                              <InlineLink href={`/org/${orgSlug ?? '_'}/webhooks`}>
                                organization endpoint
                              </InlineLink>{' '}
                              to listen to events from any project in your organization.
                            </>
                          )
                        }
                        layout="vertical"
                        className="gap-2"
                      >
                        <FormField
                          control={form.control}
                          name="subscribeAll"
                          render={({ field }) => {
                            const subscribeAllId = 'subscribe-all-events'
                            return (
                              <div className="rounded-md border bg-surface-100 overflow-hidden">
                                <Label
                                  htmlFor={subscribeAllId}
                                  className={cn(
                                    'flex w-full cursor-pointer items-center gap-3 px-4 py-3',
                                    field.value ? 'bg-surface-100' : 'bg-surface-200'
                                  )}
                                >
                                  <FormControl>
                                    <Checkbox
                                      id={subscribeAllId}
                                      checked={field.value}
                                      onCheckedChange={(checked) => {
                                        const nextValue = Boolean(checked)
                                        field.onChange(nextValue)

                                        if (nextValue) {
                                          form.setValue('eventTypes', eventTypes, {
                                            shouldDirty: true,
                                            shouldValidate: true,
                                          })
                                          return
                                        }

                                        form.setValue('eventTypes', [], {
                                          shouldDirty: true,
                                          shouldValidate: true,
                                        })
                                      }}
                                    />
                                  </FormControl>
                                  <span className="text-sm text-foreground">
                                    Subscribe to all events{' '}
                                    <code className="text-code-inline">(*)</code>
                                  </span>
                                </Label>
                              </div>
                            )
                          }}
                        />

                        <FormControl>
                          <Accordion
                            type="multiple"
                            value={openEventGroups}
                            onValueChange={setOpenEventGroups}
                            className="mt-2 space-y-2"
                          >
                            {groupedEventTypes.map((group) => {
                              const selectedInGroup = group.eventTypes.filter((eventType) =>
                                selectedTypes.includes(eventType)
                              )
                              const allSelected = selectedInGroup.length === group.eventTypes.length
                              const isGroupOpen = openEventGroups.includes(group.id)

                              return (
                                <AccordionItem
                                  key={group.id}
                                  value={group.id}
                                  className={cn(
                                    'overflow-hidden rounded-md border',
                                    hasEventTypeError && 'border-destructive-400'
                                  )}
                                >
                                  <AccordionTrigger
                                    hideIcon
                                    className="group px-4 py-3 hover:no-underline"
                                  >
                                    <div className="flex w-full items-center justify-between gap-3">
                                      <div className="flex items-center gap-2">
                                        <p className="text-sm text-foreground-light">
                                          {group.label}
                                        </p>
                                        {selectedInGroup.length > 0 && (
                                          <span className="text-xs text-foreground-muted">
                                            {selectedInGroup.length}
                                          </span>
                                        )}
                                      </div>
                                      <div className="flex items-center gap-3">
                                        {isGroupOpen && group.eventTypes.length > 1 && (
                                          <span
                                            className="text-xs text-foreground-muted hover:text-foreground"
                                            onClick={(event) => {
                                              event.preventDefault()
                                              event.stopPropagation()
                                              field.onChange(
                                                toggleEventTypeGroup(
                                                  selectedTypes,
                                                  group.eventTypes,
                                                  !allSelected
                                                )
                                              )
                                            }}
                                          >
                                            {allSelected ? 'Clear all' : 'Select all'}
                                          </span>
                                        )}
                                        <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200 group-data-open:rotate-180" />
                                      </div>
                                    </div>
                                  </AccordionTrigger>
                                  <AccordionContent className="pb-0 pt-0 [&>div]:pb-0 [&>div]:pt-0">
                                    <div className="divide-y border-t">
                                      {group.eventTypes.map((eventType) => {
                                        const checked = selectedTypes.includes(eventType)
                                        const eventTypeId = toControlId('event-type', eventType)

                                        return (
                                          <Label
                                            key={eventType}
                                            htmlFor={eventTypeId}
                                            className={cn(
                                              'flex w-full cursor-pointer items-center gap-3 px-4 py-3 transition-colors hover:bg-surface-200',
                                              checked && 'bg-surface-100'
                                            )}
                                          >
                                            <Checkbox
                                              id={eventTypeId}
                                              checked={checked}
                                              onCheckedChange={(next) => {
                                                field.onChange(
                                                  toggleEventType(
                                                    selectedTypes,
                                                    eventType,
                                                    Boolean(next)
                                                  )
                                                )
                                              }}
                                            />
                                            <code className="text-code-inline">{eventType}</code>
                                          </Label>
                                        )
                                      })}
                                    </div>
                                  </AccordionContent>
                                </AccordionItem>
                              )
                            })}
                          </Accordion>
                        </FormControl>
                      </FormItemLayout>
                    )
                  }}
                />
              </div>

              <Separator />

              <div className="px-5 space-y-3">
                <FormItemLayout
                  label={
                    <>
                      Custom headers <span className="text-foreground-muted">(optional)</span>
                    </>
                  }
                  description="Optional HTTP headers sent with every delivery."
                  layout="vertical"
                  className="gap-3"
                >
                  <KeyValueFieldArray
                    control={form.control}
                    name="customHeaders"
                    keyFieldName="key"
                    valueFieldName="value"
                    createEmptyRow={() => ({ key: '', value: '' })}
                    keyPlaceholder="Header name"
                    valuePlaceholder="Header value"
                    addLabel="Add header"
                  />
                </FormItemLayout>
              </div>
            </form>
          </Form>
        </SheetSection>
        <SheetFooter>
          <Button type="default" onClick={confirmOnClose}>
            Cancel
          </Button>
          <Button form="platform-webhook-endpoint-form" htmlType="submit">
            {mode === 'create' ? 'Create endpoint' : 'Save changes'}
          </Button>
        </SheetFooter>
      </SheetContent>
      <DiscardChangesConfirmationDialog {...discardChangesModalProps} />
    </Sheet>
  )
}
