"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { Info, Search, Trash2 } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import * as React from "react";
import { Controller, useForm } from "react-hook-form";
import { toast } from "sonner";

import { DateField } from "@/components/form/fields/date-field";
import { FormField } from "@/components/form/form-field";
import { ErrorBanner } from "@/components/shared/error-banner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { requestDashboardApi } from "@/lib/dashboard-api-client";

import {
  applyCorporateAction,
  BULK_EDIT_HREF,
  cancelCorporateActionRun,
  CORPORATE_ACTIONS_HREF,
  saveCorporateAction,
} from "../_lib/corporate-action-client-api";
import { countTrxTenants } from "../_lib/group-preview-rows";
import {
  bulkEditFormSchema,
  RATIO_OPERATORS,
  type BulkEditFormValues,
  type PreviewResult,
  type RatioOperator,
  type RunStatus,
} from "../_lib/schema";
import { usePreviewSelection } from "../_lib/use-preview-selection";
import { ApplyOptionsDialog } from "./apply-options-dialog";
import { ApplyProgress } from "./apply-progress";
import { PreviewTable } from "./preview-table";
import { RatioHint, RatioInput } from "./ratio-input";
import { UnderlyingCombobox } from "./underlying-combobox";
import { errorMessageOr, toastApiError } from "@/lib/toast-api-error";

function toRatioOperator(value: string): RatioOperator {
  return RATIO_OPERATORS.includes(value as RatioOperator)
    ? (value as RatioOperator)
    : "*";
}

/**
 * The backend stores the factor as DECIMAL(32,15), so it comes back as e.g. 4
 * or 2.5 — rendered without trailing zeroes so the field reads the way the
 * operator originally typed it.
 */
function formatFactor(value: number): string {
  if (!Number.isFinite(value)) return "";
  return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(6)));
}

type Props = {
  prefillIsin?: string;
  /** Present in edit mode: the action whose parameters and rows get rebuilt. */
  initialPreview?: PreviewResult;
  loadError?: string;
};

export function BulkEditForm({ prefillIsin, initialPreview, loadError }: Props) {
  const router = useRouter();
  const editingActionId = initialPreview?.action.id ?? null;

  const [preview, setPreview] = React.useState<PreviewResult | null>(
    initialPreview ?? null,
  );
  // The values the current preview was built from. A preview is not stored,
  // so Save has to send these again — not whatever the form says now, which
  // may have been edited since without a re-preview.
  const [previewValues, setPreviewValues] = React.useState<BulkEditFormValues | null>(null);
  const { selectedIds, toggleIds, resetSelection } = usePreviewSelection(
    initialPreview?.rows ?? [],
  );
  const [isPreviewing, setIsPreviewing] = React.useState(false);
  const [isSaving, setIsSaving] = React.useState(false);
  const [dialogOpen, setDialogOpen] = React.useState(false);
  const [run, setRun] = React.useState<RunStatus | null>(null);
  const [errorMessage, setErrorMessage] = React.useState<string | null>(
    loadError ?? null,
  );
  const [applySummary, setApplySummary] = React.useState<string | null>(null);

  const form = useForm<BulkEditFormValues>({
    resolver: zodResolver(bulkEditFormSchema),
    defaultValues: initialPreview
      ? {
          underlyingIsin: initialPreview.action.underlyingIsin,
          underlyingName: initialPreview.action.underlyingName ?? "",
          startDate: initialPreview.action.startDate ?? "",
          endDate: initialPreview.action.endDate ?? "",
          operator: toRatioOperator(initialPreview.action.operator),
          factor: formatFactor(initialPreview.action.factor),
          notes: initialPreview.action.notes ?? "",
        }
      : {
          underlyingIsin: prefillIsin ?? "",
          underlyingName: "",
          startDate: "",
          endDate: "",
          operator: "*",
          factor: "",
          notes: "",
        },
  });

  const values = form.watch();
  const isApplied = preview?.action.status === "applied";

  const tenantCount = React.useMemo(
    () => (preview ? countTrxTenants(preview.rows) : 0),
    [preview],
  );

  const onPreview = form.handleSubmit(async (submitted) => {
    setIsPreviewing(true);
    setErrorMessage(null);
    setApplySummary(null);
    setRun(null);

    try {
      // In edit mode the same action is rewritten in place, so its id and
      // history entry survive rather than a second draft piling up.
      const result = await requestDashboardApi<PreviewResult & { status?: string }>({
        url: editingActionId ? `${BULK_EDIT_HREF}/update` : `${BULK_EDIT_HREF}/preview`,
        method: "POST",
        body: editingActionId
          ? { actionId: editingActionId, values: submitted }
          : submitted,
        fallbackError: editingActionId
          ? "Could not update the corporate action."
          : "Could not build the corporate action preview.",
      });

      setPreview(result);
      setPreviewValues(submitted);
      resetSelection(result.rows);

      if (result.rows.length === 0) {
        toast.info("No records match this underlying and date range.");
      }
    } catch (error) {
      setPreview(null);
      setPreviewValues(null);
      resetSelection([]);
      setErrorMessage(
        errorMessageOr(
          error,
          editingActionId
            ? "Could not update the corporate action."
            : "Could not build the corporate action preview.",
        ),
      );
    } finally {
      setIsPreviewing(false);
    }
  });

  const onDiscard = async () => {
    if (!editingActionId) return;

    setIsSaving(true);
    try {
      await requestDashboardApi({
        url: `${BULK_EDIT_HREF}/discard`,
        method: "POST",
        body: { actionId: editingActionId },
        fallbackError: "Could not discard the corporate action.",
      });
      toast.success("Corporate action discarded.");
      router.push(CORPORATE_ACTIONS_HREF);
    } catch (error) {
      toastApiError(error, "Could not discard the corporate action.");
    } finally {
      setIsSaving(false);
    }
  };

  const onCancelRun = async () => {
    if (!preview) return;

    setIsSaving(true);
    try {
      await cancelCorporateActionRun(preview.action.id);
      // Back to an editable draft so the window can be corrected and re-previewed.
      setRun(null);
      toast.success("Queued run cancelled. You can edit and re-preview now.");
      router.push(`${BULK_EDIT_HREF}?action=${preview.action.id}`);
      router.refresh();
    } catch (error) {
      toastApiError(error, "Could not cancel the queued run.");
    } finally {
      setIsSaving(false);
    }
  };

  const onApply = async (confirmIsin: string) => {
    if (!preview) return;

    // An unsaved preview has no action id: Save creates the action, stages the
    // rows and queues the run in one call, carrying the ticks as row keys. An
    // existing action (edit mode) is applied by id as before.
    const isUnsaved = preview.action.id <= 0;
    if (isUnsaved && !previewValues) return;

    setIsSaving(true);
    try {
      const result =
        isUnsaved && previewValues
          ? await saveCorporateAction({
              values: previewValues,
              selectedRowKeys: preview.rows
                .filter((row) => selectedIds.has(row.id))
                .map((row) => row.rowKey),
              confirmIsin,
            })
          : await applyCorporateAction({
              actionId: preview.action.id,
              selectedRowIds: [...selectedIds],
              underlyingIsin: preview.action.underlyingIsin,
              confirmIsin,
            });

      setDialogOpen(false);

      // Cancel-run and the history link need the id the save just minted.
      if (result.action) {
        const saved = result.action;
        setPreview((current) => (current ? { ...current, action: saved } : current));
      }

      if (result.run) {
        setRun(result.run);
        toast.success(isUnsaved ? "Corporate action saved and queued." : "Split queued.");
        return;
      }

      // No run came back, so nothing was scheduled — report rather than imply
      // the split is under way.
      const failed = result.failed ?? 0;
      setApplySummary(
        `${result.applied ?? 0} record(s) updated${failed > 0 ? `, ${failed} failed` : ""}.`,
      );
      if (failed > 0) {
        setErrorMessage(result.errors?.slice(0, 5).join(" ") ?? null);
        toast.warning(`Applied with ${failed} failure(s).`);
      }
    } catch (error) {
      toastApiError(error, "Could not apply the corporate action.");
    } finally {
      setIsSaving(false);
    }
  };

  return (
    <div className="space-y-6">
      <ErrorBanner message={errorMessage ?? undefined} />

      <form onSubmit={onPreview} className="space-y-4 rounded-lg border bg-card p-4">
        <div className="grid gap-4 md:grid-cols-2">
          <Controller
            control={form.control}
            name="underlyingIsin"
            render={({ field, fieldState }) => (
              <FormField
                label="Underlying ISIN / Name"
                htmlFor="underlyingIsin"
                required
                error={fieldState.error}
                description="The security that split — searched across all five underlying slots."
              >
                <UnderlyingCombobox
                  id="underlyingIsin"
                  value={field.value}
                  name={values.underlyingName || null}
                  onSelect={(option) => {
                    field.onChange(option?.isin ?? "");
                    form.setValue("underlyingName", option?.name ?? "");
                  }}
                />
              </FormField>
            )}
          />

          <FormField
            label="Ratio or Factor"
            htmlFor="factor"
            required
            error={form.formState.errors.factor}
          >
            <RatioInput
              id="factor"
              operator={values.operator}
              factor={values.factor}
              onOperatorChange={(operator) =>
                form.setValue("operator", operator, { shouldValidate: true })
              }
              onFactorChange={(factor) =>
                form.setValue("factor", factor, { shouldValidate: true })
              }
            />
          </FormField>
        </div>

        <p className="flex items-start gap-1.5 text-xs">
          <Info className="mt-0.5 size-3.5 shrink-0 text-muted-foreground" />
          <RatioHint operator={values.operator} factor={values.factor} />
        </p>

        <div className="grid gap-4 md:grid-cols-2">
          <DateField
            control={form.control}
            name="startDate"
            label="Start Date"
            description="The split date. Transactions on or after it are adjusted."
            inputClassName="h-9"
          />
          <DateField
            control={form.control}
            name="endDate"
            label="End Date"
            description="Leave empty for no upper bound."
            inputClassName="h-9"
          />
        </div>

        <FormField label="Notes" htmlFor="notes" error={form.formState.errors.notes}>
          <Input
            id="notes"
            {...form.register("notes")}
            placeholder="Optional — e.g. Netflix 1:10 split, effective 2026-02-01"
          />
        </FormField>

        <div className="flex items-center gap-2">
          <Button type="submit" disabled={isPreviewing}>
            <Search className="size-4" />
            {isPreviewing
              ? editingActionId
                ? "Rebuilding preview…"
                : "Building preview…"
              : editingActionId
                ? "Update & rebuild preview"
                : "Preview changes"}
          </Button>
          {editingActionId ? (
            <Button
              type="button"
              variant="outline"
              onClick={onDiscard}
              disabled={isSaving || isPreviewing}
            >
              <Trash2 className="size-4" />
              Discard
            </Button>
          ) : null}
          <Button type="button" variant="ghost" asChild>
            <Link href={CORPORATE_ACTIONS_HREF}>View past corporate actions</Link>
          </Button>
        </div>
      </form>

      {preview ? (
        <div className="space-y-4">
          {preview.warnings.length > 0 ? (
            <div className="space-y-1 rounded-lg border border-amber-500/50 bg-amber-500/5 p-3 text-sm">
              {preview.warnings.map((warning) => (
                <p key={warning}>{warning}</p>
              ))}
            </div>
          ) : null}

          {applySummary ? (
            <div className="rounded-lg border border-emerald-500/50 bg-emerald-500/5 p-3 text-sm">
              {applySummary}
            </div>
          ) : null}

          {run ? (
            <ApplyProgress
              run={run}
              onCancel={onCancelRun}
              isCancelling={isSaving}
            />
          ) : null}

          <PreviewTable
            rows={preview.rows}
            selectedIds={selectedIds}
            onToggleIds={toggleIds}
            readOnly={isApplied || run !== null}
          />

          {!isApplied && run === null ? (
            <div className="flex items-center justify-between gap-4">
              <p className="text-muted-foreground text-sm">
                {selectedIds.size} of {preview.rows.length} row(s) selected.
              </p>
              <Button
                type="button"
                onClick={() => setDialogOpen(true)}
                disabled={selectedIds.size === 0}
              >
                Save
              </Button>
            </div>
          ) : null}

          <ApplyOptionsDialog
            open={dialogOpen}
            onOpenChange={setDialogOpen}
            underlyingIsin={preview.action.underlyingIsin}
            ratio={preview.action.ratio}
            counts={preview.counts}
            selectedCount={selectedIds.size}
            tenantCount={tenantCount}
            startDate={preview.action.startDate}
            isSaving={isSaving}
            onApply={onApply}
          />
        </div>
      ) : null}
    </div>
  );
}
