import { useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { isAxiosError } from 'axios'
import { AlertCircle } from 'lucide-react'
import { toast } from 'sonner'
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Alert, AlertDescription } from '@/components/ui/alert'
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select'
import { useMoveRubro } from '../hooks/useMoveRubro'
import type { Rubro, RubroTreeNode } from '../types'

// ─── Helper: flatten tree excluding a subtree rooted at excludedId ────────────

export interface FlatNode {
  id: number
  nombre: string
  depth: number
}

export function flattenExcludingSubtree(
  tree: RubroTreeNode[],
  excludedId: number,
): FlatNode[] {
  const result: FlatNode[] = []

  function walk(nodes: RubroTreeNode[], depth: number) {
    for (const node of nodes) {
      if (node.id === excludedId) {
        // Skip this entire subtree (node + all descendants)
        continue
      }
      result.push({ id: node.id, nombre: node.nombre, depth })
      if (node.hijos.length > 0) {
        walk(node.hijos, depth + 1)
      }
    }
  }

  walk(tree, 0)
  return result
}

// ─── Schema ───────────────────────────────────────────────────────────────────

const moveRubroSchema = z.object({
  nuevoParentId: z
    .string()
    .transform((val) => (val === 'root' ? null : Number(val)))
    .pipe(z.number().int().positive().nullable()),
  nuevoOrden: z
    .string()
    .transform((val) => (val === '' ? 0 : Number(val)))
    .pipe(z.number().int().min(0, 'El orden debe ser 0 o mayor')),
})

// Raw form field types (what useForm sees — strings before zod transforms).
type MoveRubroFormRaw = {
  nuevoParentId: string
  nuevoOrden: string
}

// Output type after zod transforms run (what handleSubmit receives at runtime
// thanks to zodResolver). We type-cast to reconcile with SubmitHandler<Raw>.
type MoveRubroFormOutput = {
  nuevoParentId: number | null
  nuevoOrden: number
}

// ─── Error resolver ───────────────────────────────────────────────────────────

function resolveMoveError(err: unknown): string | null {
  if (!err) return null
  if (isAxiosError(err) && err.response?.data) {
    const data = err.response.data as { error?: string; message?: string }
    return data.message ?? data.error ?? 'Error al mover el rubro'
  }
  // Handle raw rejection objects (from tests)
  const errObj = err as { response?: { status?: number; data?: { message?: string } } }
  if (errObj?.response?.data?.message) {
    return errObj.response.data.message
  }
  return 'Error al mover el rubro'
}

// ─── Props ────────────────────────────────────────────────────────────────────

interface MoveRubroDialogProps {
  open: boolean
  onOpenChange: (open: boolean) => void
  rubro: Rubro | null
  tree: RubroTreeNode[]
  onConfirmed?: () => void
}

// ─── Component ────────────────────────────────────────────────────────────────

export function MoveRubroDialog({
  open,
  onOpenChange,
  rubro,
  tree,
  onConfirmed,
}: MoveRubroDialogProps) {
  const [backendError, setBackendError] = useState<string | null>(null)
  const { mutateAsync, isPending } = useMoveRubro()

  const availableParents = rubro ? flattenExcludingSubtree(tree, rubro.id) : []

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const form = useForm<MoveRubroFormRaw>({
    resolver: zodResolver(moveRubroSchema) as any,
    defaultValues: {
      nuevoParentId: rubro?.parentId != null ? String(rubro.parentId) : 'root',
      nuevoOrden: String(rubro?.orden ?? 0),
    },
  })

  useEffect(() => {
    if (open && rubro) {
      setBackendError(null)
      form.reset({
        nuevoParentId: rubro.parentId != null ? String(rubro.parentId) : 'root',
        nuevoOrden: String(rubro.orden ?? 0),
      })
    }
  }, [open, rubro, form])

  async function handleSubmit(data: MoveRubroFormOutput) {
    if (!rubro) return
    setBackendError(null)

    const { nuevoParentId, nuevoOrden } = data

    try {
      await mutateAsync({ id: rubro.id, data: { nuevoParentId, nuevoOrden } })
      toast.success('Rubro movido')
      onOpenChange(false)
      onConfirmed?.()
    } catch (err) {
      const msg = resolveMoveError(err)
      setBackendError(msg)
      if (
        !isAxiosError(err) ||
        (err.response?.status !== 409 && err.response?.status !== 422 && err.response?.status !== 400)
      ) {
        toast.error('Error al mover el rubro')
      }
    }
  }

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-md">
        <DialogHeader>
          <DialogTitle>Mover rubro</DialogTitle>
          <DialogDescription>
            Seleccioná el nuevo padre y orden para &ldquo;{rubro?.nombre ?? ''}&rdquo;.
          </DialogDescription>
        </DialogHeader>

        <Form {...form}>
          <form
            onSubmit={form.handleSubmit(
              handleSubmit as unknown as Parameters<typeof form.handleSubmit>[0],
            )}
            className="space-y-4"
            noValidate
          >
            {backendError && (
              <Alert variant="destructive">
                <AlertCircle className="h-4 w-4" />
                <AlertDescription>{backendError}</AlertDescription>
              </Alert>
            )}

            <FormField
              control={form.control}
              name="nuevoParentId"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Nuevo padre</FormLabel>
                  <Select
                    value={field.value}
                    onValueChange={field.onChange}
                    disabled={isPending}
                  >
                    <FormControl>
                      <SelectTrigger>
                        <SelectValue placeholder="Seleccioná el padre…" />
                      </SelectTrigger>
                    </FormControl>
                    <SelectContent>
                      <SelectItem value="root">Raíz (sin padre)</SelectItem>
                      {availableParents.map((node) => (
                        <SelectItem key={node.id} value={String(node.id)}>
                          {'—'.repeat(node.depth)} {node.nombre}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                  <FormMessage />
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="nuevoOrden"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Orden</FormLabel>
                  <FormControl>
                    <Input
                      {...field}
                      type="number"
                      min={0}
                      disabled={isPending}
                      placeholder="0"
                    />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />

            <div className="flex justify-end gap-2 pt-2">
              <Button
                type="button"
                variant="outline"
                onClick={() => onOpenChange(false)}
                disabled={isPending}
              >
                Cancelar
              </Button>
              <Button type="submit" disabled={isPending}>
                {isPending ? 'Moviendo...' : 'Mover'}
              </Button>
            </div>
          </form>
        </Form>
      </DialogContent>
    </Dialog>
  )
}
