'use client';

import { useState } from 'react';
import { useAction, useMutation, useQuery } from 'convex/react';
import { FileText, KeyRound, Trash2, Upload } from 'lucide-react';
import { toast } from 'sonner';

import type { Id } from '@spoon/backend/convex/_generated/dataModel.js';
import { api } from '@spoon/backend/convex/_generated/api.js';
import {
  Button,
  Card,
  CardContent,
  CardHeader,
  CardTitle,
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
  Input,
  Label,
  Textarea,
} from '@spoon/ui';

type ParsedSecret = {
  name: string;
  value: string;
};

type ParseResult = {
  secrets: ParsedSecret[];
  skipped: string[];
};

const secretNamePattern = /^[A-Z_][A-Z0-9_]*$/;

const unquoteValue = (value: string) => {
  const trimmed = value.trim();
  if (
    (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
    (trimmed.startsWith("'") && trimmed.endsWith("'"))
  ) {
    const inner = trimmed.slice(1, -1);
    return trimmed.startsWith('"')
      ? inner
          .replaceAll('\\n', '\n')
          .replaceAll('\\r', '\r')
          .replaceAll('\\t', '\t')
          .replaceAll('\\"', '"')
          .replaceAll('\\\\', '\\')
      : inner;
  }
  return trimmed;
};

const parseDotenv = (source: string): ParseResult => {
  const secrets = new Map<string, ParsedSecret>();
  const skipped: string[] = [];

  source.split(/\r?\n/).forEach((rawLine, index) => {
    const line = rawLine.trim();
    if (!line || line.startsWith('#')) return;

    const withoutExport = line.startsWith('export ')
      ? line.slice('export '.length).trim()
      : line;
    const equalsIndex = withoutExport.indexOf('=');
    if (equalsIndex <= 0) {
      skipped.push(`Line ${index + 1}: missing KEY=value`);
      return;
    }

    const name = withoutExport.slice(0, equalsIndex).trim().toUpperCase();
    const value = unquoteValue(withoutExport.slice(equalsIndex + 1));
    if (!secretNamePattern.test(name)) {
      skipped.push(`Line ${index + 1}: invalid secret name`);
      return;
    }
    if (!value) {
      skipped.push(`Line ${index + 1}: empty value`);
      return;
    }
    secrets.set(name, { name, value });
  });

  return { secrets: [...secrets.values()], skipped };
};

export const SpoonSecretsForm = ({ spoonId }: { spoonId: Id<'spoons'> }) => {
  const secrets = useQuery(api.spoonSecrets.listForSpoon, { spoonId }) ?? [];
  const createSecret = useAction(api.spoonSecretsNode.create);
  const removeSecret = useMutation(api.spoonSecrets.remove);
  const [name, setName] = useState('');
  const [value, setValue] = useState('');
  const [description, setDescription] = useState('');
  const [bulkText, setBulkText] = useState('');
  const [saving, setSaving] = useState(false);
  const [bulkSaving, setBulkSaving] = useState(false);
  const [bulkDialogOpen, setBulkDialogOpen] = useState(false);
  const parsedBulk = parseDotenv(bulkText);

  const save = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setSaving(true);
    try {
      await createSecret({
        spoonId,
        name,
        value,
        description: description || undefined,
      });
      setName('');
      setValue('');
      setDescription('');
      toast.success('Spoon secret saved.');
    } catch (error) {
      console.error(error);
      toast.error('Could not save secret.');
    } finally {
      setSaving(false);
    }
  };

  const importBulk = async () => {
    if (!parsedBulk.secrets.length) {
      toast.error('No valid secrets found.');
      return;
    }
    setBulkSaving(true);
    try {
      for (const secret of parsedBulk.secrets) {
        await createSecret({
          spoonId,
          name: secret.name,
          value: secret.value,
          description: 'Imported from .env',
        });
      }
      setBulkText('');
      setBulkDialogOpen(false);
      toast.success(`${parsedBulk.secrets.length} secrets imported.`);
    } catch (error) {
      console.error(error);
      toast.error('Could not import secrets.');
    } finally {
      setBulkSaving(false);
    }
  };

  const readEnvFile = async (file?: File) => {
    if (!file) return;
    try {
      setBulkText(await file.text());
    } catch (error) {
      console.error(error);
      toast.error('Could not read .env file.');
    }
  };

  return (
    <Card className='shadow-none'>
      <CardHeader className='flex-row items-center justify-between gap-4'>
        <CardTitle className='flex items-center gap-2 text-base'>
          <KeyRound className='size-4' />
          Project secrets
        </CardTitle>
        <Dialog open={bulkDialogOpen} onOpenChange={setBulkDialogOpen}>
          <DialogTrigger asChild>
            <Button type='button' variant='outline' size='sm'>
              <Upload className='size-4' />
              Import .env
            </Button>
          </DialogTrigger>
          <DialogContent className='sm:max-w-2xl'>
            <DialogHeader>
              <DialogTitle>Import project secrets</DialogTitle>
              <DialogDescription>
                Paste a dotenv file or upload one. Existing names are updated,
                and values are encrypted before storage.
              </DialogDescription>
            </DialogHeader>

            <div className='space-y-4'>
              <div className='grid gap-2'>
                <Label htmlFor='env-file'>Upload .env file</Label>
                <Input
                  id='env-file'
                  type='file'
                  accept='.env,.txt,text/plain'
                  onChange={(event) =>
                    void readEnvFile(event.target.files?.[0])
                  }
                />
              </div>
              <div className='grid gap-2'>
                <Label htmlFor='env-paste'>Paste .env content</Label>
                <Textarea
                  id='env-paste'
                  value={bulkText}
                  className='min-h-56 font-mono text-xs'
                  placeholder={'AUTHENTIK_CLIENT_ID=...\nAUTHENTIK_SECRET=...'}
                  onChange={(event) => setBulkText(event.target.value)}
                />
              </div>
              <div className='border-border bg-muted/30 rounded-md border p-3 text-sm'>
                <p className='font-medium'>
                  {parsedBulk.secrets.length} valid secret
                  {parsedBulk.secrets.length === 1 ? '' : 's'} ready to import
                </p>
                {parsedBulk.secrets.length ? (
                  <div className='text-muted-foreground mt-2 flex flex-wrap gap-2'>
                    {parsedBulk.secrets.slice(0, 12).map((secret) => (
                      <span
                        key={secret.name}
                        className='bg-background rounded border px-2 py-1 font-mono text-xs'
                      >
                        {secret.name}
                      </span>
                    ))}
                    {parsedBulk.secrets.length > 12 ? (
                      <span className='px-2 py-1 text-xs'>
                        +{parsedBulk.secrets.length - 12} more
                      </span>
                    ) : null}
                  </div>
                ) : null}
                {parsedBulk.skipped.length ? (
                  <div className='text-muted-foreground mt-3 space-y-1 text-xs'>
                    {parsedBulk.skipped.slice(0, 4).map((line) => (
                      <p key={line}>{line}</p>
                    ))}
                    {parsedBulk.skipped.length > 4 ? (
                      <p>+{parsedBulk.skipped.length - 4} more skipped lines</p>
                    ) : null}
                  </div>
                ) : null}
              </div>
            </div>

            <DialogFooter>
              <Button
                type='button'
                onClick={() => void importBulk()}
                disabled={bulkSaving || !parsedBulk.secrets.length}
              >
                <FileText className='size-4' />
                {bulkSaving ? 'Importing...' : 'Import secrets'}
              </Button>
            </DialogFooter>
          </DialogContent>
        </Dialog>
      </CardHeader>
      <CardContent className='space-y-4'>
        <form
          onSubmit={save}
          className='grid gap-3 md:grid-cols-[1fr_1fr_1fr_auto]'
        >
          <div className='grid gap-2'>
            <Label htmlFor='secretName'>Name</Label>
            <Input
              id='secretName'
              value={name}
              placeholder='AUTHENTIK_CLIENT_ID'
              onChange={(event) => setName(event.target.value)}
            />
          </div>
          <div className='grid gap-2'>
            <Label htmlFor='secretValue'>Value</Label>
            <Input
              id='secretValue'
              type='password'
              value={value}
              onChange={(event) => setValue(event.target.value)}
            />
          </div>
          <div className='grid gap-2'>
            <Label htmlFor='secretDescription'>Description</Label>
            <Input
              id='secretDescription'
              value={description}
              placeholder='Used for local validation'
              onChange={(event) => setDescription(event.target.value)}
            />
          </div>
          <div className='flex items-end'>
            <Button type='submit' disabled={saving}>
              Save
            </Button>
          </div>
        </form>
        <div className='divide-border overflow-hidden rounded-md border'>
          {secrets.length ? (
            secrets.map((secret) => (
              <div
                key={secret._id}
                className='flex items-center justify-between gap-3 border-b p-3'
              >
                <div>
                  <p className='font-mono text-sm'>{secret.name}</p>
                  <p className='text-muted-foreground text-xs'>
                    {secret.description ?? secret.valuePreview ?? 'Configured'}
                  </p>
                </div>
                <Button
                  type='button'
                  variant='ghost'
                  size='icon'
                  aria-label={`Remove ${secret.name}`}
                  onClick={async () => {
                    await removeSecret({ secretId: secret._id });
                    toast.success('Secret removed.');
                  }}
                >
                  <Trash2 className='size-4' />
                </Button>
              </div>
            ))
          ) : (
            <p className='text-muted-foreground p-3 text-sm'>
              No secrets saved for this Spoon.
            </p>
          )}
        </div>
      </CardContent>
    </Card>
  );
};
