import type { Route } from './+types/edit'

import { zodResolver } from '@hookform/resolvers/zod'
import { FileBadgeIcon, FileCode2Icon, MoreHorizontalIcon } from 'lucide-react'
import { Suspense, useState, type ReactNode } from 'react'
import { useForm } from 'react-hook-form'
import { Await, useAsyncValue, useFetcher } from 'react-router'
import { toast } from 'sonner'
import { z } from 'zod'

import { request as req, HttpMethod } from '@repo/util/request'
import { Skeleton } from '@repo/ui/components/skeleton'
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator
} from '@repo/ui/components/ui/breadcrumb'
import { Button } from '@repo/ui/components/ui/button'
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle
} from '@repo/ui/components/ui/card'
import { Checkbox } from '@repo/ui/components/ui/checkbox'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger
} from '@repo/ui/components/ui/dropdown-menu'
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage
} from '@repo/ui/components/ui/form'
import { Input } from '@repo/ui/components/ui/input'
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput
} from '@repo/ui/components/ui/input-group'
import { Spinner } from '@repo/ui/components/ui/spinner'
import { Switch } from '@repo/ui/components/ui/switch'

const formSchema = z
  .object({
    given_cert: z.coerce.boolean(),
    never_expires: z.coerce.boolean(),
    name: z.string().min(1),
    access_period: z.coerce.number().min(1),
    cert: z.object({
      exp_interval: z.coerce.number()
    }),
    rawfile: z
      .instanceof(File, { message: 'Anexe um arquivo HTML' })
      .optional(),
    unlisted: z.boolean()
  })
  .refine(
    (data) => {
      if (data?.never_expires) {
        return true
      }

      return data?.cert?.exp_interval > 0
    },
    {
      message: 'Deve ser maior ou igual a 1',
      path: ['cert', 'exp_interval']
    }
  )

type Schema = z.infer<typeof formSchema>

type Cert = {
  exp_interval: number
  s3_uri?: string
}

export type Course = {
  id: string
  name: string
  access_period: number
  cert?: Cert
  unlisted?: boolean
}

export function meta({}: Route.MetaArgs) {
  return [{ title: 'Editar curso' }]
}

export const loader = async ({
  params,
  context,
  request
}: Route.LoaderArgs) => {
  const r = await req({
    url: `/courses/${params.id}`,
    context,
    request
  })

  if (!r.ok) {
    throw new Response(null, { status: r.status })
  }

  return { data: r.json() as Promise<Course> }
}

export async function action({ params, request, context }: Route.ActionArgs) {
  const formData = await request.formData()

  const r = await req({
    url: `courses/${params.id}`,
    method: HttpMethod.PUT,
    body: formData,
    request,
    context
  })

  return { ok: r.status === 200 }
}

export default function Component({ loaderData: { data } }) {
  return (
    <Suspense fallback={<Skeleton />}>
      <div className="space-y-4">
        <Breadcrumb>
          <BreadcrumbList>
            <BreadcrumbItem>
              <BreadcrumbLink href="/">Cursos</BreadcrumbLink>
            </BreadcrumbItem>
            <BreadcrumbSeparator />
            <BreadcrumbItem>
              <BreadcrumbPage>Editar curso</BreadcrumbPage>
            </BreadcrumbItem>
          </BreadcrumbList>
        </Breadcrumb>

        <div className="lg:max-w-2xl mx-auto">
          <Await resolve={data}>
            <Editing />
          </Await>
        </div>
      </div>
    </Suspense>
  )
}

function Editing() {
  const course = useAsyncValue() as Course
  const fetcher = useFetcher()

  const form = useForm({
    resolver: zodResolver(formSchema),
    defaultValues: {
      unlisted: false,
      given_cert: !!course?.cert,
      never_expires: !course?.cert?.exp_interval,
      ...course
    }
  })
  const { handleSubmit, formState, watch } = form
  const givenCert = watch('given_cert')
  const neverExpires = watch('never_expires') as boolean

  const onSubmit = async ({ given_cert, never_expires, ...data }: Schema) => {
    const formData = new FormData()
    const data_ = flattenObject(data, '.')

    for (const k in data_) {
      // @ts-ignore
      const v = data_[k]

      if (v) {
        formData.append(k, v)
      }
    }

    // If `s3_uri` exists, restore it
    if (course?.cert?.s3_uri) {
      formData.append('cert.s3_uri', course.cert.s3_uri)
    }

    await fetcher.submit(formData, {
      method: 'post',
      encType: 'multipart/form-data'
    })

    toast.success('O curso foi atualizado')
  }

  return (
    <Form {...form}>
      <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
        <Card>
          <CardHeader>
            <CardTitle className="text-2xl">Editar curso</CardTitle>
            <CardDescription>
              Configurar as informações gerais para este curso
            </CardDescription>
          </CardHeader>
          <CardContent className="space-y-4">
            <FormField
              control={form.control}
              name="name"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Curso</FormLabel>
                  <FormControl>
                    <Input {...field} />
                  </FormControl>

                  <FormMessage />
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="access_period"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>
                    Tempo de acesso
                    <span className="text-xs text-white/30 font-normal">
                      (em dias)
                    </span>
                  </FormLabel>
                  <FormControl>
                    <Input type="number" {...field} />
                  </FormControl>

                  <FormMessage />
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="given_cert"
              render={({ field }) => (
                <FormItem
                  className="flex flex-row items-center justify-between
                             rounded-lg border p-3 shadow-sm
                             dark:has-aria-checked:border-blue-900
                             dark:has-aria-checked:bg-blue-950"
                >
                  <div className="space-y-1">
                    <FormLabel>Habilitar certificação</FormLabel>
                    <FormDescription>
                      Emita automaticamente o certificado de conclusão para os
                      participantes.
                    </FormDescription>
                  </div>
                  <FormControl>
                    <Switch
                      checked={field.value}
                      onCheckedChange={field.onChange}
                      className="cursor-pointer"
                    />
                  </FormControl>
                </FormItem>
              )}
            />

            {givenCert ? (
              <div className="space-y-4 border rounded-lg p-4 bg-accent/50">
                <h3 className="font-medium">Configurações do certificado</h3>

                <div className="space-y-1.5">
                  <FormField
                    control={form.control}
                    name="cert.exp_interval"
                    disabled={neverExpires}
                    defaultValue={''}
                    render={({ field }) => (
                      <FormItem>
                        <FormLabel>
                          Período de validade
                          <span className="text-xs text-white/30 font-normal">
                            (em dias)
                          </span>
                        </FormLabel>
                        <FormControl>
                          <Input
                            type="number"
                            className="disabled:text-transparent"
                            {...field}
                          />
                        </FormControl>

                        <FormMessage />
                      </FormItem>
                    )}
                  />

                  <FormField
                    name="never_expires"
                    control={form.control}
                    render={({ field: { onChange, value, ...field } }) => {
                      return (
                        <FormItem className="flex items-center gap-1.5">
                          <FormControl>
                            <Checkbox
                              checked={value}
                              onCheckedChange={onChange}
                              {...field}
                            />
                          </FormControl>
                          <FormLabel className="font-normal text-muted-foreground">
                            O certificado não possui prazo de validade.
                          </FormLabel>
                        </FormItem>
                      )
                    }}
                  />
                </div>

                <FormField
                  control={form.control}
                  name="rawfile"
                  render={({ field: { onChange, value, ...field } }) => (
                    <FormItem>
                      <FormLabel className="flex justify-between gap-4">
                        <span>Template do certificado</span>
                      </FormLabel>
                      <FormControl>
                        <InputGroup>
                          <InputGroupInput
                            type="file"
                            accept="text/html"
                            onChange={(e) => {
                              const file = e.target.files?.[0]

                              if (file) {
                                onChange(file)
                              }
                            }}
                            {...field}
                          />

                          {course?.cert?.s3_uri ? (
                            <InputGroupAddon align="inline-end">
                              <DownloadMenu
                                course_id={course.id}
                                s3_uri={course?.cert?.s3_uri}
                              />
                            </InputGroupAddon>
                          ) : null}
                        </InputGroup>
                      </FormControl>
                      <FormDescription>
                        Anexe o arquivo HTML que será utilizado na emissão dos
                        certificados.
                      </FormDescription>
                      <FormMessage />
                    </FormItem>
                  )}
                />
              </div>
            ) : null}

            <FormField
              control={form.control}
              name="unlisted"
              render={({ field: { onChange, value, ...field } }) => (
                <FormItem className="flex items-center gap-2">
                  <FormControl>
                    <Checkbox
                      checked={value}
                      onCheckedChange={onChange}
                      {...field}
                    />
                  </FormControl>
                  <FormLabel>Não listar no catálogo de cursos</FormLabel>
                </FormItem>
              )}
            />
          </CardContent>
        </Card>

        <div className="flex justify-end">
          <Button
            type="submit"
            className="cursor-pointer"
            disabled={formState.isSubmitting}
          >
            {formState.isSubmitting && <Spinner />}
            Editar
          </Button>
        </div>
      </form>
    </Form>
  )
}

type DownloadMenuProps = {
  course_id: string
  s3_uri: string
}

function DownloadMenu({ course_id, s3_uri }: DownloadMenuProps) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <InputGroupButton
          variant="ghost"
          aria-label="Mais"
          size="icon-xs"
          className="cursor-pointer"
        >
          <MoreHorizontalIcon />
        </InputGroupButton>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DownloadMenuItem
          course_id={course_id}
          s3_uri={s3_uri}
          url={`/api/courses/${course_id}/sample`}
        >
          {({ isLoading }) => (
            <>{isLoading ? <Spinner /> : <FileBadgeIcon />} Baixar amostra</>
          )}
        </DownloadMenuItem>

        <DownloadMenuItem
          course_id={course_id}
          s3_uri={s3_uri}
          url={`/api/courses/${course_id}/template`}
        >
          {({ isLoading }) => (
            <>
              {isLoading ? <Spinner /> : <FileCode2Icon />}
              <span>Baixar template</span>
              <span className="text-xs text-muted-foreground">(html)</span>
            </>
          )}
        </DownloadMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

type DownloadMenuItemProps = {
  course_id: string
  url: string
  s3_uri: string
  children: (props: { isLoading: boolean }) => ReactNode
}

function DownloadMenuItem({
  children,
  course_id,
  url,
  s3_uri
}: DownloadMenuItemProps) {
  const [isLoading, setIsLoading] = useState(false)

  const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()
    setIsLoading(true)

    const r = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ s3_uri })
    })

    setIsLoading(false)

    if (r.ok) {
      const url = URL.createObjectURL(await r.blob())
      const link = document.createElement('a')

      link.href = url
      link.download = course_id
      document.body.appendChild(link)

      link.click()
      link.remove()

      URL.revokeObjectURL(url)
    }
  }

  return (
    <DropdownMenuItem asChild>
      <button className="w-full cursor-pointer" onClick={handleClick}>
        {children({ isLoading })}
      </button>
    </DropdownMenuItem>
  )
}

export function flattenObject<
  T extends Record<string, any>,
  R extends Record<string, any> = Record<string, any>
>(obj: T, delimiter: string = '.', prefix: string = ''): R {
  return Object.keys(obj).reduce((acc, key) => {
    const value = obj[key]
    const newKey = prefix ? `${prefix}${delimiter}${key}` : key

    if (
      typeof value === 'object' &&
      value !== null &&
      !Array.isArray(value) &&
      Object.keys(value).length > 0
    ) {
      Object.assign(acc, flattenObject(value, delimiter, newKey))
    } else {
      ;(acc as any)[newKey] = value
    }

    return acc
  }, {} as R)
}
