/**
 * Create-source dialog. Renders as a Radix Dialog triggered by a
 * Button. The form collects the source fields, then on success
 * a SECOND dialog opens showing the one-time secrets payload
 * (HMAC + API key). The operator must click "I have saved
 * these" before the modal closes.
 *
 * The server returns secrets ONLY on create when the caller
 * supplied hmac_secret / api_key OR the operator clicked the
 * "auto-generate" checkbox. The modal is shown only when the
 * response contains a `secrets` block.
 *
 * Renders nothing if the user lacks create-source permission
 * (super_admin + tenant_admin).
 */

import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Copy, Eye, EyeOff, Plus, ShieldCheck } from 'lucide-react';
import { toast } from 'sonner';

import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';
import { Checkbox } from '@/components/ui/checkbox';

import { getErrorMessage, useCreateSource } from './api';
import type { CreateOrRotateResponse, SecretsPayload, Source } from './types';

const ID_RE = /^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$/;

const formSchema = z.object({
  id: z
    .string()
    .min(2, 'Source id must be 2–64 characters')
    .max(64, 'Source id must be 2–64 characters')
    .regex(ID_RE, 'Lowercase letters, digits, and dashes only'),
  name: z.string().min(1, 'Name is required').max(200, 'Name must be \u2264 200 characters'),
  type: z.enum(['http', 'mqtt', 'ws', 'grpc']),
  rate_limit_per_sec: z
    .number({ invalid_type_error: 'Enter a number' })
    .int('Whole number only')
    .min(1, 'Must be at least 1')
    .max(1_000_000, 'Must be at most 1,000,000'),
  description: z.string().max(500, 'Description must be \u2264 500 characters').optional().or(z.literal('')),
  mtls_required: z.boolean(),
  auto_secrets: z.boolean(), // if true, server generates them
  hmac_secret: z.string().optional().or(z.literal('')),
  api_key: z.string().optional().or(z.literal('')),
});

type FormValues = z.infer<typeof formSchema>;

const EMPTY_DEFAULTS = {
  id: '',
  name: '',
  type: 'http' as const,
  rate_limit_per_sec: 1000,
  description: '',
  mtls_required: false,
  auto_secrets: true,
  hmac_secret: '',
  api_key: '',
};

export function CreateSourceDialog({ tenantId }: { tenantId: string }) {
  const [open, setOpen] = useState(false);
  const [secretsModal, setSecretsModal] = useState<CreateOrRotateResponse | null>(null);
  const create = useCreateSource(tenantId);

  const form = useForm({
    resolver: zodResolver(formSchema),
    defaultValues: EMPTY_DEFAULTS,
  });

  // Reset form when the dialog opens.
  useEffect(() => {
    if (open) form.reset(EMPTY_DEFAULTS);
  }, [open, form]);

  const onSubmit = form.handleSubmit(async (values) => {
    const body = {
      id: values.id.trim(),
      name: values.name.trim(),
      type: values.type,
      rate_limit_per_sec: values.rate_limit_per_sec,
      description: values.description ?? '',
      mtls_required: values.mtls_required,
      // If auto_secrets is true, omit the fields so the server
      // generates them and returns them in the response. If
      // false and the fields are non-empty, send them.
      hmac_secret: values.auto_secrets ? undefined : values.hmac_secret || undefined,
      api_key: values.auto_secrets ? undefined : values.api_key || undefined,
    };
    try {
      const resp = await create.mutateAsync(body);
      setOpen(false);
      if (resp.secrets) {
        setSecretsModal(resp);
      } else {
        toast.success(`Source "${resp.source.name}" created.`);
      }
    } catch (err) {
      toast.error(getErrorMessage(err));
    }
  });

  return (
    <>
      <Dialog open={open} onOpenChange={setOpen}>
        <DialogTrigger asChild>
          <Button>
            <Plus className="mr-2 h-4 w-4" />
            New source
          </Button>
        </DialogTrigger>
        <DialogContent className="sm:max-w-lg">
          <DialogHeader>
            <DialogTitle>New source</DialogTitle>
            <DialogDescription>
              Create a new ingest endpoint for this company. Secrets are
              generated and shown <strong>only once</strong>; save them
              before closing the next dialog.
            </DialogDescription>
          </DialogHeader>
          <form onSubmit={onSubmit} className="flex flex-col gap-4">
            <div className="grid grid-cols-2 gap-3">
              <div className="col-span-1 flex flex-col gap-1">
                <Label htmlFor="src-id">ID</Label>
                <Input id="src-id" placeholder="primary" {...form.register('id')} />
                {form.formState.errors.id ? (
                  <p className="text-xs text-destructive">{form.formState.errors.id.message}</p>
                ) : null}
              </div>
              <div className="col-span-1 flex flex-col gap-1">
                <Label htmlFor="src-type">Type</Label>
                <select
                  id="src-type"
                  className="h-9 rounded-md border border-input bg-background px-3 text-sm"
                  {...form.register('type')}
                >
                  <option value="http">HTTP</option>
                  <option value="mqtt">MQTT</option>
                  <option value="ws">WebSocket</option>
                  <option value="grpc">gRPC</option>
                </select>
              </div>
            </div>
            <div className="flex flex-col gap-1">
              <Label htmlFor="src-name">Name</Label>
              <Input id="src-name" placeholder="Primary Source" {...form.register('name')} />
              {form.formState.errors.name ? (
                <p className="text-xs text-destructive">{form.formState.errors.name.message}</p>
              ) : null}
            </div>
            <div className="grid grid-cols-2 gap-3">
              <div className="col-span-1 flex flex-col gap-1">
                <Label htmlFor="src-rl">Rate limit (/s)</Label>
                <Input
                  id="src-rl"
                  type="number"
                  min={1}
                  {...form.register('rate_limit_per_sec', { valueAsNumber: true })}
                />
                {form.formState.errors.rate_limit_per_sec ? (
                  <p className="text-xs text-destructive">
                    {form.formState.errors.rate_limit_per_sec.message}
                  </p>
                ) : null}
              </div>
              <div className="col-span-1 flex items-center gap-2 pt-6">
                <Checkbox id="src-mtls" {...form.register('mtls_required')} />
                <Label htmlFor="src-mtls" className="cursor-pointer">Require mTLS</Label>
              </div>
            </div>
            <div className="flex flex-col gap-1">
              <Label htmlFor="src-desc">Description</Label>
              <Textarea
                id="src-desc"
                rows={2}
                placeholder="Optional. What does this source do?"
                {...form.register('description')}
              />
            </div>

            <div className="rounded-md border bg-muted/30 p-3">
              <div className="flex items-center gap-2">
                <Checkbox id="src-auto" {...form.register('auto_secrets')} />
                <Label htmlFor="src-auto" className="cursor-pointer">
                  Auto-generate HMAC secret + API key
                </Label>
              </div>
              {form.watch('auto_secrets') ? null : (
                <div className="mt-3 grid grid-cols-1 gap-2">
                  <div className="flex flex-col gap-1">
                    <Label htmlFor="src-hmac" className="text-xs">HMAC secret (32–128 chars)</Label>
                    <Input id="src-hmac" {...form.register('hmac_secret')} />
                  </div>
                  <div className="flex flex-col gap-1">
                    <Label htmlFor="src-apikey" className="text-xs">API key (16–128 chars)</Label>
                    <Input id="src-apikey" {...form.register('api_key')} />
                  </div>
                </div>
              )}
            </div>

            <DialogFooter>
              <Button type="button" variant="ghost" onClick={() => setOpen(false)}>
                Cancel
              </Button>
              <Button type="submit" disabled={create.isPending}>
                {create.isPending ? 'Creating…' : 'Create source'}
              </Button>
            </DialogFooter>
          </form>
        </DialogContent>
      </Dialog>

      <SecretsModal
        state={secretsModal}
        onClose={() => {
          if (secretsModal) {
            toast.success(`Source "${secretsModal.source.name}" created.`);
          }
          setSecretsModal(null);
        }}
      />
    </>
  );
}

/**
 * SecretsModal — the one-time secrets display. Forced
 * confirmation: the operator must click "I have saved these"
 * to dismiss. Until then, the secrets stay in the DOM (so
 * screen-readers can read them) but the visible content
 * defaults to masked.
 */
function SecretsModal({ state, onClose }: { state: CreateOrRotateResponse | null; onClose: () => void }) {
  return (
    <Dialog open={Boolean(state)} onOpenChange={(open) => !open && onClose()}>
      <DialogContent className="sm:max-w-lg">
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2">
            <ShieldCheck className="h-5 w-5 text-amber-500" />
            Save these secrets now
          </DialogTitle>
          <DialogDescription>
            These are the HMAC secret and API key for the source. They are
            shown <strong>once</strong>. After you close this dialog, only
            the hashes are stored; the plaintext is gone from the server.
          </DialogDescription>
        </DialogHeader>
        {state ? <SecretsView secrets={state.secrets} /> : null}
        <DialogFooter>
          <Button onClick={onClose}>I have saved these — close</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function SecretsView({ secrets }: { secrets: SecretsPayload | undefined }) {
  const [showHmac, setShowHmac] = useState(false);
  const [showApi, setShowApi] = useState(false);
  return (
    <div className="flex flex-col gap-3">
      <SecretRow
        label="HMAC secret"
        value={secrets?.hmac_secret ?? ''}
        show={showHmac}
        onToggle={() => setShowHmac((v) => !v)}
      />
      <SecretRow
        label="API key"
        value={secrets?.api_key ?? ''}
        show={showApi}
        onToggle={() => setShowApi((v) => !v)}
      />
    </div>
  );
}

function SecretRow({ label, value, show, onToggle }: { label: string; value: string; show: boolean; onToggle: () => void }) {
  const [copied, setCopied] = useState(false);
  return (
    <div className="flex flex-col gap-1">
      <div className="flex items-center justify-between">
        <span className="text-xs font-medium text-muted-foreground">{label}</span>
        <div className="flex items-center gap-1">
          <Button type="button" size="sm" variant="ghost" onClick={onToggle} aria-label={show ? 'Hide' : 'Show'}>
            {show ? <EyeOff className="h-3 w-3" /> : <Eye className="h-3 w-3" />}
          </Button>
          <Button
            type="button"
            size="sm"
            variant="ghost"
            onClick={async () => {
              try {
                await navigator.clipboard.writeText(value);
                setCopied(true);
                setTimeout(() => setCopied(false), 1500);
              } catch {
                // ignore
              }
            }}
            aria-label="Copy"
          >
            <Copy className="h-3 w-3" />
            <span className="ml-1 text-xs">{copied ? 'Copied' : 'Copy'}</span>
          </Button>
        </div>
      </div>
      <pre className="overflow-x-auto rounded-md border bg-muted/40 p-2 text-xs leading-relaxed">
        <code>{show ? value : '\u2022'.repeat(Math.min(value.length || 8, 32))}</code>
      </pre>
    </div>
  );
}
