"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 { MasterTableDateField } from "../../../_components/master-table-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 {
  bulkEditFormSchema,
  RATIO_OPERATORS,
  type BulkEditFormValues,
  type CorporateActionMode,
  type PreviewResult,
  type RatioOperator,
  type RunStatus,
} from "../_lib/schema";
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";

const BASE_HREF = "/dashboard/master-table/structure-master/bulk-edit";
const HISTORY_HREF = "/dashboard/master-table/structure-master/corporate-actions";

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 ApplyResponse = {
  status?: string;
  message?: string;
  applied?: number;
  failed?: number;
  errors?: string[];
  run?: RunStatus | null;
};

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,
  );
  const [selectedIds, setSelectedIds] = React.useState<Set<number>>(
    () =>
      new Set(
        (initialPreview?.rows ?? [])
          .filter((row) => !row.applied && row.selected)
          .map((row) => row.id),
      ),
  );
  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(() => {
    if (!preview) return 0;
    const customers = new Set(
      preview.rows
        .filter((row) => row.scope === "trx" && row.customerId !== null)
        .map((row) => row.customerId),
    );
    return customers.size;
  }, [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 ? `${BASE_HREF}/update` : `${BASE_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);
      // Staged rows start ticked, matching the spec's "Checkbox (Update)"
      // column — except any the backend unticked because an earlier action
      // already split that record. Re-ticking those here would undo the guard.
      setSelectedIds(
        new Set(
          result.rows
            .filter((row) => !row.applied && row.selected)
            .map((row) => row.id),
        ),
      );

      if (result.rows.length === 0) {
        toast.info("No records match this underlying and date range.");
      }
    } catch (error) {
      setPreview(null);
      setSelectedIds(new Set());
      setErrorMessage(
        error instanceof Error
          ? error.message
          : 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: `${BASE_HREF}/discard`,
        method: "POST",
        body: { actionId: editingActionId },
        fallbackError: "Could not discard the corporate action.",
      });
      toast.success("Corporate action discarded.");
      router.push(HISTORY_HREF);
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "Could not discard the corporate action.",
      );
    } finally {
      setIsSaving(false);
    }
  };

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

    setIsSaving(true);
    try {
      await requestDashboardApi({
        url: `${BASE_HREF}/cancel-run`,
        method: "POST",
        body: { actionId: preview.action.id },
        fallbackError: "Could not cancel the queued run.",
      });
      // 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(`${BASE_HREF}?action=${preview.action.id}`);
      router.refresh();
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "Could not cancel the queued run.",
      );
    } finally {
      setIsSaving(false);
    }
  };

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

    setIsSaving(true);
    try {
      const result = await requestDashboardApi<ApplyResponse>({
        url: `${BASE_HREF}/apply`,
        method: "POST",
        body: {
          actionId: preview.action.id,
          selectedRowIds: [...selectedIds],
          underlyingIsin: preview.action.underlyingIsin,
          confirmIsin,
        },
        fallbackError: "Could not apply the corporate action.",
      });

      setDialogOpen(false);

      if (result.run) {
        setRun(result.run);
        toast.success("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) {
      toast.error(
        error instanceof Error ? error.message : "Could not apply the corporate action.",
      );
    } finally {
      setIsSaving(false);
    }
  };

  const toggleRow = (id: number, selected: boolean) => {
    setSelectedIds((current) => {
      const next = new Set(current);
      if (selected) next.add(id);
      else next.delete(id);
      return next;
    });
  };

  const toggleAll = (selected: boolean) => {
    if (!preview) return;
    setSelectedIds(
      selected
        ? new Set(preview.rows.filter((row) => !row.applied).map((row) => row.id))
        : new Set(),
    );
  };

  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">
          <MasterTableDateField
            control={form.control}
            name="startDate"
            label="Start Date"
            description="The split date. Transactions on or after it are adjusted."
          />
          <MasterTableDateField
            control={form.control}
            name="endDate"
            label="End Date"
            description="Leave empty for no upper bound."
          />
        </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={HISTORY_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}
            onToggle={toggleRow}
            onToggleAll={toggleAll}
            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>
  );
}
