import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { z } from 'zod';
import { Loader2 } from 'lucide-react';
import { toast } from 'sonner';
import {
  Button,
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
  Input,
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@vector/ui';
import { logRepair } from '../../lib/api/repairs.js';
import { listHosts } from '../../lib/api/hosts.js';
import { listManufacturers } from '../../lib/api/manufacturers.js';
import { listFms } from '../../lib/api/fms.js';
import { listParts } from '../../lib/api/parts.js';
import { ApiRequestError } from '../../lib/api/client.js';
import { queryKeys } from '../../lib/queryKeys.js';
import type { PartModel, Repair } from '../../lib/api/types.js';
import { PartModelCombobox } from '../common/PartModelCombobox.js';

// When the broken serial matches an existing Part the model fields are skipped entirely;
// otherwise the tech either picks an existing PartModel (partModelId) or types a new MPN
// and a manufacturer. The refine mirrors LogRepairRequest.superRefine on the server.
const Schema = z
  .object({
    hostId: z.string().uuid('Pick a host'),
    brokenSerial: z.string().trim().min(1, 'Required').max(128),
    brokenPartModelId: z.string().uuid().optional(),
    brokenMpn: z.string().trim().max(128).optional(),
    brokenManufacturerId: z.string().uuid().optional(),
    replacementSerial: z.string().trim().min(1, 'Required').max(128),
    fmId: z.string().optional(),
    brokenExists: z.boolean().optional(),
  })
  .superRefine((v, ctx) => {
    if (v.brokenExists) return;
    const hasModel = Boolean(v.brokenPartModelId);
    const hasNew = Boolean(v.brokenMpn && v.brokenMpn.length > 0);
    if (!hasModel && !hasNew) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: 'Pick a part model or enter a new MPN',
        path: ['brokenPartModelId'],
      });
    }
    if (hasNew && !v.brokenManufacturerId) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: 'Select a manufacturer for the new model',
        path: ['brokenManufacturerId'],
      });
    }
  });
type Values = z.infer<typeof Schema>;

const NO_FM = '__none__';

interface LogRepairDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onLogged?: (repair: Repair) => void;
}

export function LogRepairDialog({ open, onOpenChange, onLogged }: LogRepairDialogProps) {
  const queryClient = useQueryClient();

  const [pickedModel, setPickedModel] = useState<PartModel | null>(null);

  const form = useForm<Values>({
    resolver: zodResolver(Schema),
    defaultValues: {
      hostId: '',
      brokenSerial: '',
      brokenPartModelId: '',
      brokenMpn: '',
      brokenManufacturerId: '',
      replacementSerial: '',
      fmId: '',
      brokenExists: false,
    },
  });

  useEffect(() => {
    if (!open) return;
    setPickedModel(null);
    form.reset({
      hostId: '',
      brokenSerial: '',
      brokenPartModelId: '',
      brokenMpn: '',
      brokenManufacturerId: '',
      replacementSerial: '',
      fmId: '',
      brokenExists: false,
    });
  }, [open, form]);

  const hostId = form.watch('hostId');
  const brokenSerial = form.watch('brokenSerial').trim();

  const hosts = useQuery({
    queryKey: queryKeys.hosts.list({ pageSize: 100 }),
    queryFn: () => listHosts({ pageSize: 100 }),
    enabled: open,
  });

  const manufacturers = useQuery({
    queryKey: queryKeys.manufacturers.list({ pageSize: 100 }),
    queryFn: () => listManufacturers({ pageSize: 100 }),
    enabled: open,
  });

  // Open FMs on the chosen host, so the optional linker only shows relevant items.
  const openFms = useQuery({
    queryKey: queryKeys.fms.list({ hostId, status: 'OPEN', pageSize: 50 }),
    queryFn: () => listFms({ hostId, status: 'OPEN', pageSize: 50 }),
    enabled: open && Boolean(hostId),
  });

  // Debounced live lookup — as the tech types a broken serial, hint whether Vector
  // already knows that part (existing) or will auto-ingest it (new).
  const brokenLookup = useQuery({
    queryKey: queryKeys.parts.list({ serialNumber: brokenSerial, pageSize: 1 }),
    queryFn: () => listParts({ serialNumber: brokenSerial, pageSize: 1 }),
    enabled: open && brokenSerial.length >= 3,
    staleTime: 5_000,
  });
  const existingBroken = brokenLookup.data?.data.find(
    (p) => p.serialNumber === brokenSerial,
  );

  // Keep a form-level flag so the zod refine can skip model validation when the broken part
  // is already in the catalog (server just reuses the existing PartModel).
  useEffect(() => {
    form.setValue('brokenExists', Boolean(existingBroken), { shouldValidate: true });
  }, [existingBroken, form]);

  const mutation = useMutation({
    mutationFn: (v: Values) => {
      const base = {
        hostId: v.hostId,
        brokenSerial: v.brokenSerial.trim(),
        replacementSerial: v.replacementSerial.trim(),
        fmId: v.fmId ? v.fmId : undefined,
      };
      // If the broken part is already catalogued, the server ignores model fields entirely.
      if (existingBroken) return logRepair(base);
      const modelPayload = v.brokenPartModelId
        ? { brokenPartModelId: v.brokenPartModelId }
        : {
            brokenMpn: v.brokenMpn?.trim(),
            brokenManufacturerId: v.brokenManufacturerId,
          };
      return logRepair({ ...base, ...modelPayload });
    },
    onSuccess: (repair) => {
      toast.success('Repair logged');
      queryClient.invalidateQueries({ queryKey: queryKeys.repairs.all });
      queryClient.invalidateQueries({ queryKey: queryKeys.parts.all });
      queryClient.invalidateQueries({ queryKey: queryKeys.custody.all });
      onOpenChange(false);
      onLogged?.(repair);
    },
    onError: (err) =>
      toast.error(err instanceof ApiRequestError ? err.body.message : 'Could not log repair'),
  });

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="max-w-lg">
        <DialogHeader>
          <DialogTitle>Log a repair</DialogTitle>
          <DialogDescription>
            Record a physical part swap. The broken part goes into your custody until you drop it
            in a bin from the My Custody page.
          </DialogDescription>
        </DialogHeader>

        <Form {...form}>
          <form
            onSubmit={form.handleSubmit((v) => mutation.mutate(v))}
            className="space-y-3"
          >
            <FormField
              control={form.control}
              name="hostId"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Host</FormLabel>
                  <Select value={field.value} onValueChange={field.onChange}>
                    <FormControl>
                      <SelectTrigger>
                        <SelectValue placeholder="Select host" />
                      </SelectTrigger>
                    </FormControl>
                    <SelectContent>
                      {hosts.data?.data.map((h) => (
                        <SelectItem key={h.id} value={h.id}>
                          {h.assetId} — {h.name}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                  <FormMessage />
                </FormItem>
              )}
            />

            <div className="grid grid-cols-2 gap-3">
              <FormField
                control={form.control}
                name="brokenSerial"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Broken serial</FormLabel>
                    <FormControl>
                      <Input autoFocus placeholder="SN-…" {...field} />
                    </FormControl>
                    {brokenSerial.length >= 3 && (
                      <FormDescription>
                        {brokenLookup.isFetching
                          ? 'Looking up…'
                          : existingBroken
                            ? `Found: ${existingBroken.partModel.mpn}`
                            : 'Will be ingested as a new part.'}
                      </FormDescription>
                    )}
                    <FormMessage />
                  </FormItem>
                )}
              />
              <FormField
                control={form.control}
                name="replacementSerial"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Replacement serial</FormLabel>
                    <FormControl>
                      <Input placeholder="SN-…" {...field} />
                    </FormControl>
                    <FormDescription>Must be an existing SPARE.</FormDescription>
                    <FormMessage />
                  </FormItem>
                )}
              />
            </div>

            {!existingBroken && (
              <>
                <FormField
                  control={form.control}
                  name="brokenPartModelId"
                  render={() => (
                    <FormItem>
                      <FormLabel>Broken part model</FormLabel>
                      <PartModelCombobox
                        value={pickedModel}
                        newMpn={form.watch('brokenMpn') || null}
                        onPick={(m) => {
                          setPickedModel(m);
                          form.setValue('brokenPartModelId', m.id, { shouldValidate: true });
                          form.setValue('brokenMpn', '');
                          form.setValue('brokenManufacturerId', '');
                        }}
                        onCreateNew={(mpn) => {
                          setPickedModel(null);
                          form.setValue('brokenPartModelId', '');
                          form.setValue('brokenMpn', mpn, { shouldValidate: true });
                        }}
                        onClear={() => {
                          setPickedModel(null);
                          form.setValue('brokenPartModelId', '');
                          form.setValue('brokenMpn', '');
                          form.setValue('brokenManufacturerId', '');
                        }}
                      />
                      <FormMessage />
                    </FormItem>
                  )}
                />

                {!pickedModel && form.watch('brokenMpn') && (
                  <FormField
                    control={form.control}
                    name="brokenManufacturerId"
                    render={({ field }) => (
                      <FormItem>
                        <FormLabel>Manufacturer (for new model)</FormLabel>
                        <Select value={field.value ?? ''} onValueChange={field.onChange}>
                          <FormControl>
                            <SelectTrigger>
                              <SelectValue placeholder="Select" />
                            </SelectTrigger>
                          </FormControl>
                          <SelectContent>
                            {manufacturers.data?.data.map((m) => (
                              <SelectItem key={m.id} value={m.id}>
                                {m.name}
                              </SelectItem>
                            ))}
                          </SelectContent>
                        </Select>
                        <FormMessage />
                      </FormItem>
                    )}
                  />
                )}
              </>
            )}

            <FormField
              control={form.control}
              name="fmId"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Link to open FM (optional)</FormLabel>
                  <Select
                    value={field.value ? field.value : NO_FM}
                    onValueChange={(v) => field.onChange(v === NO_FM ? '' : v)}
                    disabled={!hostId}
                  >
                    <FormControl>
                      <SelectTrigger>
                        <SelectValue placeholder={hostId ? 'No linked FM' : 'Pick a host first'} />
                      </SelectTrigger>
                    </FormControl>
                    <SelectContent>
                      <SelectItem value={NO_FM}>No linked FM</SelectItem>
                      {openFms.data?.data.map((f) => (
                        <SelectItem key={f.id} value={f.id}>
                          {f.problem.slice(0, 80)}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                  <FormDescription>
                    Linking doesn't auto-close the FM.
                  </FormDescription>
                  <FormMessage />
                </FormItem>
              )}
            />

            <DialogFooter>
              <Button
                type="button"
                variant="outline"
                onClick={() => onOpenChange(false)}
                disabled={mutation.isPending}
              >
                Cancel
              </Button>
              <Button type="submit" disabled={mutation.isPending}>
                {mutation.isPending && <Loader2 className="h-4 w-4 animate-spin" />}
                Log repair
              </Button>
            </DialogFooter>
          </form>
        </Form>
      </DialogContent>
    </Dialog>
  );
}
