import { type ReactNode, useState } from 'react';

import { Trans, useLingui } from '@lingui/react/macro';
import { EnvelopeType } from '@prisma/client';
import { Loader } from 'lucide-react';
import {
  ErrorCode as DropzoneErrorCode,
  ErrorCode,
  type FileRejection,
  useDropzone,
} from 'react-dropzone';
import { Link, useNavigate, useParams } from 'react-router';
import { match } from 'ts-pattern';

import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import type { TCreateEnvelopePayload } from '@documenso/trpc/server/envelope-router/create-envelope.types';
import { cn } from '@documenso/ui/lib/utils';
import { useToast } from '@documenso/ui/primitives/use-toast';

import { RecipientDetectionPromptDialog } from '~/components/dialogs/recipient-detection-prompt-dialog';
import { SuggestedRecipientsDialog } from '~/components/dialogs/suggested-recipients-dialog';
import { useCurrentTeam } from '~/providers/team';
import {
  type RecipientForCreation,
  detectRecipientsInDocument,
  ensureRecipientEmails,
} from '~/utils/detect-document-recipients';

export interface EnvelopeDropZoneWrapperProps {
  children: ReactNode;
  type: EnvelopeType;
  className?: string;
}

export const EnvelopeDropZoneWrapper = ({
  children,
  type,
  className,
}: EnvelopeDropZoneWrapperProps) => {
  const { t } = useLingui();
  const { toast } = useToast();
  const { user } = useSession();
  const { folderId } = useParams();

  const team = useCurrentTeam();

  const navigate = useNavigate();
  const analytics = useAnalytics();
  const organisation = useCurrentOrganisation();

  const [isLoading, setIsLoading] = useState(false);
  const [showExtractionPrompt, setShowExtractionPrompt] = useState(false);
  const [uploadedDocumentId, setUploadedDocumentId] = useState<string | null>(null);
  const [pendingRecipients, setPendingRecipients] = useState<RecipientForCreation[] | null>(null);
  const [showRecipientsDialog, setShowRecipientsDialog] = useState(false);
  const [shouldNavigateAfterPromptClose, setShouldNavigateAfterPromptClose] = useState(true);

  const userTimezone =
    TIME_ZONES.find((timezone) => timezone === Intl.DateTimeFormat().resolvedOptions().timeZone) ??
    DEFAULT_DOCUMENT_TIME_ZONE;

  const { quota, remaining, refreshLimits, maximumEnvelopeItemCount } = useLimits();

  const { mutateAsync: createEnvelope } = trpc.envelope.create.useMutation();
  const { mutateAsync: createRecipients } = trpc.envelope.recipient.createMany.useMutation();

  const isUploadDisabled = remaining.documents === 0 || !user.emailVerified;

  const onFileDrop = async (files: File[]) => {
    if (isUploadDisabled && IS_BILLING_ENABLED()) {
      await navigate(`/o/${organisation.url}/settings/billing`);
      return;
    }

    try {
      setIsLoading(true);

      const payload = {
        folderId,
        type,
        title: files[0].name,
        meta: {
          timezone: userTimezone,
        },
      } satisfies TCreateEnvelopePayload;

      const formData = new FormData();

      formData.append('payload', JSON.stringify(payload));

      for (const file of files) {
        formData.append('files', file);
      }

      const { id } = await createEnvelope(formData);

      void refreshLimits();

      toast({
        title: type === EnvelopeType.DOCUMENT ? t`Document uploaded` : t`Template uploaded`,
        description:
          type === EnvelopeType.DOCUMENT
            ? t`Your document has been uploaded successfully.`
            : t`Your template has been uploaded successfully.`,
        duration: 5000,
      });

      if (type === EnvelopeType.DOCUMENT) {
        analytics.capture('App: Document Uploaded', {
          userId: user.id,
          documentId: id,
          timestamp: new Date().toISOString(),
        });

        // Show AI prompt dialog for documents
        setUploadedDocumentId(id);
        setPendingRecipients(null);
        setShowRecipientsDialog(false);
        setShouldNavigateAfterPromptClose(true);
        setShowExtractionPrompt(true);
      } else {
        // Templates - navigate immediately
        const pathPrefix = formatTemplatesPath(team.url);
        await navigate(`${pathPrefix}/${id}/edit`);
      }
    } catch (err) {
      const error = AppError.parseError(err);

      const errorMessage = match(error.code)
        .with('INVALID_DOCUMENT_FILE', () => t`You cannot upload encrypted PDFs`)
        .with(
          AppErrorCode.LIMIT_EXCEEDED,
          () => t`You have reached your document limit for this month. Please upgrade your plan.`,
        )
        .with(
          'ENVELOPE_ITEM_LIMIT_EXCEEDED',
          () => t`You have reached the limit of the number of files per envelope`,
        )
        .otherwise(() => t`An error occurred during upload.`);

      toast({
        title: t`Error`,
        description: errorMessage,
        variant: 'destructive',
        duration: 7500,
      });
    } finally {
      setIsLoading(false);
    }
  };

  const onFileDropRejected = (fileRejections: FileRejection[]) => {
    if (!fileRejections.length) {
      return;
    }

    const maxItemsReached = fileRejections.some((fileRejection) =>
      fileRejection.errors.some((error) => error.code === DropzoneErrorCode.TooManyFiles),
    );

    if (maxItemsReached) {
      toast({
        title: t`You cannot upload more than ${maximumEnvelopeItemCount} items per envelope.`,
        duration: 5000,
        variant: 'destructive',
      });

      return;
    }

    // Since users can only upload only one file (no multi-upload), we only handle the first file rejection
    const { file, errors } = fileRejections[0];

    if (!errors.length) {
      return;
    }

    const errorNodes = errors.map((error, index) => (
      <span key={index} className="block">
        {match(error.code)
          .with(ErrorCode.FileTooLarge, () => (
            <Trans>File is larger than {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB</Trans>
          ))
          .with(ErrorCode.FileInvalidType, () => <Trans>Only PDF files are allowed</Trans>)
          .with(ErrorCode.FileTooSmall, () => <Trans>File is too small</Trans>)
          .with(ErrorCode.TooManyFiles, () => (
            <Trans>Only one file can be uploaded at a time</Trans>
          ))
          .otherwise(() => (
            <Trans>Unknown error</Trans>
          ))}
      </span>
    ));

    const description = (
      <>
        <span className="font-medium">
          <Trans>{file.name} couldn't be uploaded:</Trans>
        </span>
        {errorNodes}
      </>
    );

    toast({
      title: t`Upload failed`,
      description,
      duration: 5000,
      variant: 'destructive',
    });
  };

  const navigateToEnvelopeEditor = () => {
    if (!uploadedDocumentId) {
      return;
    }

    const pathPrefix = formatDocumentsPath(team.url);
    void navigate(`${pathPrefix}/${uploadedDocumentId}/edit`);
  };

  const handleStartRecipientDetection = async () => {
    if (!uploadedDocumentId) {
      return;
    }

    try {
      const recipients = await detectRecipientsInDocument(uploadedDocumentId);

      if (recipients.length === 0) {
        toast({
          title: t`No recipients detected`,
          description: t`You can add recipients manually in the editor`,
          duration: 5000,
        });

        setShouldNavigateAfterPromptClose(true);
        setShowExtractionPrompt(false);
        navigateToEnvelopeEditor();
        return;
      }

      const recipientsWithEmails = ensureRecipientEmails(recipients, uploadedDocumentId);

      setPendingRecipients(recipientsWithEmails);
      setShouldNavigateAfterPromptClose(false);
      setShowExtractionPrompt(false);
      setShowRecipientsDialog(true);
    } catch (error) {
      if (!(error instanceof Error && error.message === 'NO_RECIPIENTS_DETECTED')) {
        const parsedError = AppError.parseError(error);

        toast({
          title: t`Failed to detect recipients`,
          description: parsedError.userMessage || t`You can add recipients manually in the editor`,
          variant: 'destructive',
          duration: 7500,
        });
      }

      throw error;
    }
  };

  const handleSkipRecipientDetection = () => {
    setShouldNavigateAfterPromptClose(true);
    setShowExtractionPrompt(false);
    navigateToEnvelopeEditor();
  };

  const handleRecipientsCancel = () => {
    setShowRecipientsDialog(false);
    setPendingRecipients(null);
    navigateToEnvelopeEditor();
  };

  const handleRecipientsConfirm = async (recipientsToCreate: RecipientForCreation[]) => {
    if (!uploadedDocumentId) {
      return;
    }

    try {
      await createRecipients({
        envelopeId: uploadedDocumentId,
        data: recipientsToCreate,
      });

      toast({
        title: t`Recipients added`,
        description: t`Successfully detected ${recipientsToCreate.length} recipient(s)`,
        duration: 5000,
      });

      setShowRecipientsDialog(false);
      setPendingRecipients(null);
      navigateToEnvelopeEditor();
    } catch (error) {
      const parsedError = AppError.parseError(error);

      toast({
        title: t`Failed to add recipients`,
        description: parsedError.userMessage || t`Please review the recipients and try again`,
        variant: 'destructive',
        duration: 7500,
      });

      throw error;
    }
  };

  const handlePromptDialogOpenChange = (open: boolean) => {
    setShowExtractionPrompt(open);

    if (open) {
      setShouldNavigateAfterPromptClose(true);
      return;
    }

    if (!open && shouldNavigateAfterPromptClose) {
      navigateToEnvelopeEditor();
    }
  };

  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    accept: {
      'application/pdf': ['.pdf'],
    },
    multiple: true,
    maxSize: megabytesToBytes(APP_DOCUMENT_UPLOAD_SIZE_LIMIT),
    maxFiles: maximumEnvelopeItemCount,
    onDrop: (files) => void onFileDrop(files),
    onDropRejected: onFileDropRejected,
    noClick: true,
    noDragEventsBubbling: true,
  });

  return (
    <div {...getRootProps()} className={cn('relative min-h-screen', className)}>
      <input {...getInputProps()} />
      {children}

      {isDragActive && (
        <div className="bg-muted/60 fixed left-0 top-0 z-[9999] h-full w-full backdrop-blur-[4px]">
          <div className="pointer-events-none flex h-full w-full flex-col items-center justify-center">
            <h2 className="text-foreground text-2xl font-semibold">
              {type === EnvelopeType.DOCUMENT ? (
                <Trans>Upload Document</Trans>
              ) : (
                <Trans>Upload Template</Trans>
              )}
            </h2>

            <p className="text-muted-foreground text-md mt-4">
              <Trans>Drag and drop your PDF file here</Trans>
            </p>

            {isUploadDisabled && IS_BILLING_ENABLED() && (
              <Link
                to={`/o/${organisation.url}/settings/billing`}
                className="mt-4 text-sm text-amber-500 hover:underline dark:text-amber-400"
              >
                <Trans>Upgrade your plan to upload more documents</Trans>
              </Link>
            )}

            {!isUploadDisabled &&
              team?.id === undefined &&
              remaining.documents > 0 &&
              Number.isFinite(remaining.documents) && (
                <p className="text-muted-foreground/80 mt-4 text-sm">
                  <Trans>
                    {remaining.documents} of {quota.documents} documents remaining this month.
                  </Trans>
                </p>
              )}
          </div>
        </div>
      )}

      {isLoading && (
        <div className="bg-muted/30 absolute inset-0 z-50 backdrop-blur-[2px]">
          <div className="pointer-events-none flex h-1/2 w-full flex-col items-center justify-center">
            <Loader className="text-primary h-12 w-12 animate-spin" />
            <p className="text-foreground mt-8 font-medium">
              <Trans>Uploading</Trans>
            </p>
          </div>
        </div>
      )}

      <RecipientDetectionPromptDialog
        open={showExtractionPrompt}
        onOpenChange={handlePromptDialogOpenChange}
        onAccept={handleStartRecipientDetection}
        onSkip={handleSkipRecipientDetection}
      />

      <SuggestedRecipientsDialog
        open={showRecipientsDialog}
        recipients={pendingRecipients}
        onOpenChange={(open) => {
          if (!open) {
            handleRecipientsCancel();
          } else {
            setShowRecipientsDialog(true);
          }
        }}
        onCancel={handleRecipientsCancel}
        onSubmit={handleRecipientsConfirm}
      />
    </div>
  );
};
