"use client";

import { useEffect, useMemo, useState } from "react";
import type { EstimateDemandLineRateMode } from "@nexus/shared";
import {
  computeEvenSpread,
  getEstimateMonthRange,
  rebalanceSpread,
  summarizeMonthlySpread,
} from "@nexus/engine";
import {
  buildDemandLineMetadata,
  getEffectiveDemandLineValues,
  resolveDemandLineCalculationMetadata,
} from "~/components/estimates/EstimateWorkspace.calculations.js";
import type {
  EstimateResourceSnapshotView,
  EstimateVersionView,
  EstimateWorkspaceView,
  WorkspaceTab,
} from "~/components/estimates/EstimateWorkspace.types.js";
import {
  AssumptionEditor,
  type EditableAssumption,
} from "~/components/estimates/editors/AssumptionEditor.js";
import {
  ScopeItemEditor,
  type EditableScopeItem,
} from "~/components/estimates/editors/ScopeItemEditor.js";
import {
  DemandLineEditor,
  type EditableDemandLine,
  type ResourceOption,
} from "~/components/estimates/editors/DemandLineEditor.js";
import { formatMoney } from "~/lib/format.js";
import { trpc } from "~/lib/trpc/client.js";

interface ResourceListView {
  resources: ResourceOption[];
}

function toNumber(value: string) {
  const parsed = Number.parseFloat(value);
  return Number.isFinite(parsed) ? parsed : 0;
}

function toCents(value: string) {
  return Math.round(toNumber(value) * 100);
}

function parseAssumptionValue(valueType: string, rawValue: string) {
  if (valueType === "number") {
    return toNumber(rawValue);
  }
  if (valueType === "boolean") {
    return rawValue.trim().toLowerCase() === "true";
  }
  if (valueType === "json") {
    try {
      return rawValue.trim() ? JSON.parse(rawValue) : {};
    } catch {
      return rawValue;
    }
  }
  return rawValue;
}

function stringifyAssumptionValue(value: unknown) {
  if (typeof value === "string") {
    return value;
  }
  if (value == null) {
    return "";
  }
  if (typeof value === "object") {
    return JSON.stringify(value);
  }
  return String(value);
}

function renderEditorHint() {
  return (
    <div className="rounded-3xl border border-dashed border-gray-200 bg-white px-5 py-10 text-center text-sm text-gray-400">
      Draft editing is currently available in Overview, Assumptions, Scope Breakdown, and Staffing.
    </div>
  );
}

export function EstimateWorkspaceDraftEditor({
  estimate,
  tab,
  onCancel,
  onSaved,
}: {
  estimate: EstimateWorkspaceView;
  tab: WorkspaceTab;
  onCancel: () => void;
  onSaved: () => void | Promise<void>;
}) {
  const utils = trpc.useUtils();
  const versions = estimate.versions as EstimateVersionView[];
  const resourcesQuery = trpc.resource.listStaff.useQuery(
    { isActive: true, limit: 200 },
    { staleTime: 15_000 },
  );
  const workingVersion =
    versions.find((version) => version.status === "WORKING") ?? versions[0] ?? null;

  const [name, setName] = useState(estimate.name);
  const [opportunityId, setOpportunityId] = useState(estimate.opportunityId ?? "");
  const [baseCurrency, setBaseCurrency] = useState(estimate.baseCurrency);
  const [versionLabel, setVersionLabel] = useState(workingVersion?.label ?? "");
  const [versionNotes, setVersionNotes] = useState(workingVersion?.notes ?? "");
  const [assumptions, setAssumptions] = useState<EditableAssumption[]>([]);
  const [scopeItems, setScopeItems] = useState<EditableScopeItem[]>([]);
  const [demandLines, setDemandLines] = useState<EditableDemandLine[]>([]);
  const [error, setError] = useState<string | null>(null);
  const [scopeImportWarnings, setScopeImportWarnings] = useState<string[]>([]);
  const resourceList = (resourcesQuery.data as ResourceListView | undefined) ?? undefined;
  const resourceOptions = useMemo<ResourceOption[]>(() => {
    const resources = resourceList?.resources ?? [];

    return resources.map((resource) => ({
      id: resource.id,
      eid: resource.eid,
      displayName: resource.displayName,
      chapter: resource.chapter ?? null,
      roleId: resource.roleId ?? null,
      lcrCents: resource.lcrCents,
      ucrCents: resource.ucrCents,
      currency: resource.currency,
      dynamicFields:
        typeof resource.dynamicFields === "object" &&
        resource.dynamicFields !== null &&
        !Array.isArray(resource.dynamicFields)
          ? (resource.dynamicFields as Record<string, unknown>)
          : {},
    }));
  }, [resourceList]);
  const resourceMap = useMemo(
    () => new Map(resourceOptions.map((resource) => [resource.id, resource])),
    [resourceOptions],
  );
  const snapshotByResourceId = useMemo(
    () =>
      new Map(
        (workingVersion?.resourceSnapshots ?? [])
          .filter(
            (snapshot): snapshot is EstimateResourceSnapshotView & { resourceId: string } =>
              typeof snapshot.resourceId === "string" && snapshot.resourceId.length > 0,
          )
          .map((snapshot) => [snapshot.resourceId, snapshot]),
      ),
    [workingVersion],
  );

  useEffect(() => {
    setName(estimate.name);
    setOpportunityId(estimate.opportunityId ?? "");
    setBaseCurrency(estimate.baseCurrency);
    setVersionLabel(workingVersion?.label ?? "");
    setVersionNotes(workingVersion?.notes ?? "");
    setAssumptions(
      (workingVersion?.assumptions ?? []).map((assumption) => ({
        id: assumption.id,
        category: assumption.category,
        key: assumption.key,
        label: assumption.label,
        valueType: assumption.valueType,
        value: stringifyAssumptionValue(assumption.value),
        notes: assumption.notes ?? "",
      })),
    );
    setScopeItems(
      (workingVersion?.scopeItems ?? []).map((item) => ({
        id: item.id,
        sequenceNo: String(item.sequenceNo),
        scopeType: item.scopeType,
        packageCode: item.packageCode ?? "",
        name: item.name,
        description: item.description ?? "",
      })),
    );
    setDemandLines(
      (workingVersion?.demandLines ?? []).map((line) => {
        const resourceSnapshot =
          line.resourceId != null ? snapshotByResourceId.get(line.resourceId) : null;
        const calculation = resolveDemandLineCalculationMetadata({
          resourceSnapshot,
          metadata: line.metadata,
          costRateCents: line.costRateCents,
          billRateCents: line.billRateCents,
        });

        const existingSpread =
          (line as { monthlySpread?: Record<string, number> }).monthlySpread ?? {};
        return {
          id: line.id,
          ...(line.scopeItemId ? { scopeItemId: line.scopeItemId } : {}),
          ...(line.roleId ? { roleId: line.roleId } : {}),
          ...(line.resourceId ? { resourceId: line.resourceId } : {}),
          lineType: line.lineType,
          name: line.name,
          chapter: line.chapter ?? "",
          hours: line.hours.toFixed(1),
          currency: line.currency,
          costRate: (line.costRateCents / 100).toFixed(2),
          billRate: (line.billRateCents / 100).toFixed(2),
          costRateMode: calculation.costRateMode,
          billRateMode: calculation.billRateMode,
          metadata: line.metadata ?? {},
          lockedMonths: existingSpread,
          spreadExpanded: Object.keys(existingSpread).length > 0,
        };
      }),
    );
    setError(null);
  }, [estimate, snapshotByResourceId, workingVersion]);

  const summary = useMemo(() => {
    return demandLines.reduce(
      (accumulator, line) => {
        const hours = toNumber(line.hours);
        const resourceSnapshot =
          line.resourceId != null
            ? (resourceMap.get(line.resourceId) ??
              snapshotByResourceId.get(line.resourceId) ??
              null)
            : null;
        const effectiveValues = getEffectiveDemandLineValues({
          resourceSnapshot,
          hours,
          currency: line.currency,
          defaultCurrency: baseCurrency,
          costRateCents: toCents(line.costRate),
          billRateCents: toCents(line.billRate),
          costRateMode: line.costRateMode,
          billRateMode: line.billRateMode,
        });

        return {
          totalHours: accumulator.totalHours + hours,
          totalCostCents: accumulator.totalCostCents + effectiveValues.costTotalCents,
          totalPriceCents: accumulator.totalPriceCents + effectiveValues.priceTotalCents,
        };
      },
      { totalHours: 0, totalCostCents: 0, totalPriceCents: 0 },
    );
  }, [baseCurrency, demandLines, resourceMap, snapshotByResourceId]);

  const updateMutation = trpc.estimate.updateDraft.useMutation({
    onSuccess: async () => {
      await Promise.all([
        utils.estimate.list.invalidate(),
        utils.estimate.getById.invalidate({ id: estimate.id }),
      ]);
      await onSaved();
    },
    onError: (mutationError) => {
      setError(mutationError.message);
    },
  });

  if (!workingVersion) {
    return renderEditorHint();
  }

  function getLineResourceSnapshot(line: EditableDemandLine) {
    if (!line.resourceId) {
      return null;
    }

    return resourceMap.get(line.resourceId) ?? snapshotByResourceId.get(line.resourceId) ?? null;
  }

  function getLineEffectiveValues(line: EditableDemandLine) {
    return getEffectiveDemandLineValues({
      resourceSnapshot: getLineResourceSnapshot(line),
      hours: toNumber(line.hours),
      currency: line.currency,
      defaultCurrency: baseCurrency,
      costRateCents: toCents(line.costRate),
      billRateCents: toCents(line.billRate),
      costRateMode: line.costRateMode,
      billRateMode: line.billRateMode,
    });
  }

  const projectStartDate = estimate.project?.startDate
    ? new Date(estimate.project.startDate)
    : null;
  const projectEndDate = estimate.project?.endDate ? new Date(estimate.project.endDate) : null;
  const hasProjectDates = projectStartDate !== null && projectEndDate !== null;

  function computeLineSpread(line: EditableDemandLine): Record<string, number> {
    if (!hasProjectDates) return {};
    const hours = toNumber(line.hours);
    if (hours <= 0) return {};

    const lockedKeys = Object.keys(line.lockedMonths);
    if (lockedKeys.length > 0) {
      return rebalanceSpread({
        totalHours: hours,
        startDate: projectStartDate,
        endDate: projectEndDate,
        lockedMonths: line.lockedMonths,
      }).spread;
    }

    return computeEvenSpread({
      totalHours: hours,
      startDate: projectStartDate,
      endDate: projectEndDate,
    }).spread;
  }

  const spreadMonths = hasProjectDates
    ? getEstimateMonthRange(projectStartDate, projectEndDate)
    : [];

  const aggregatedSpread = hasProjectDates
    ? summarizeMonthlySpread(demandLines.map(computeLineSpread))
    : {};

  async function handleSave() {
    setError(null);

    const sanitizedAssumptions = assumptions
      .filter((assumption) => assumption.key.trim() && assumption.label.trim())
      .map((assumption, index) => ({
        ...(assumption.id ? { id: assumption.id } : {}),
        category: assumption.category.trim() || "commercial",
        key: assumption.key.trim(),
        label: assumption.label.trim(),
        valueType: assumption.valueType.trim() || "string",
        value: parseAssumptionValue(assumption.valueType, assumption.value),
        sortOrder: index,
        ...(assumption.notes.trim() ? { notes: assumption.notes.trim() } : {}),
      }));

    const sanitizedScopeItems = scopeItems
      .filter((item) => item.name.trim())
      .map((item, index) => ({
        ...(item.id ? { id: item.id } : {}),
        sequenceNo: Math.max(1, Math.round(toNumber(item.sequenceNo) || index + 1)),
        scopeType: item.scopeType.trim() || "SHOT",
        ...(item.packageCode.trim() ? { packageCode: item.packageCode.trim() } : {}),
        name: item.name.trim(),
        ...(item.description.trim() ? { description: item.description.trim() } : {}),
        technicalSpec: {},
        sortOrder: index,
        metadata: {},
      }));

    const sanitizedDemandLines = demandLines
      .filter((line) => line.name.trim())
      .map((line) => {
        const hours = toNumber(line.hours);
        const resourceSnapshot = getLineResourceSnapshot(line);
        const effectiveValues = getLineEffectiveValues(line);
        const calculation = resolveDemandLineCalculationMetadata({
          resourceSnapshot,
          metadata: line.metadata,
          costRateCents: effectiveValues.effectiveCostRateCents,
          billRateCents: effectiveValues.effectiveBillRateCents,
        });
        return {
          ...(line.id ? { id: line.id } : {}),
          ...(line.scopeItemId ? { scopeItemId: line.scopeItemId } : {}),
          ...(line.roleId ? { roleId: line.roleId } : {}),
          ...(line.resourceId ? { resourceId: line.resourceId } : {}),
          lineType: line.lineType.trim() || "LABOR",
          name: line.name.trim(),
          ...(line.chapter.trim() ? { chapter: line.chapter.trim() } : {}),
          hours,
          costRateCents: effectiveValues.effectiveCostRateCents,
          billRateCents: effectiveValues.effectiveBillRateCents,
          currency: effectiveValues.currency,
          costTotalCents: effectiveValues.costTotalCents,
          priceTotalCents: effectiveValues.priceTotalCents,
          monthlySpread: computeLineSpread(line),
          staffingAttributes: {},
          metadata: buildDemandLineMetadata(line.metadata, {
            ...calculation,
            costRateMode: line.costRateMode,
            billRateMode: line.billRateMode,
          }),
        };
      });

    const linkedResourceIds = [
      ...new Set(
        sanitizedDemandLines
          .map((line) => line.resourceId)
          .filter(
            (resourceId): resourceId is string =>
              typeof resourceId === "string" && resourceId.length > 0,
          ),
      ),
    ];

    const sanitizedResourceSnapshots = linkedResourceIds
      .map((resourceId) => {
        const liveResource = resourceMap.get(resourceId);
        const existingSnapshot = snapshotByResourceId.get(resourceId);

        if (!liveResource && !existingSnapshot) {
          return null;
        }

        if (liveResource) {
          return {
            ...(existingSnapshot?.id ? { id: existingSnapshot.id } : {}),
            resourceId: liveResource.id,
            sourceEid: liveResource.eid,
            displayName: liveResource.displayName,
            ...(liveResource.chapter ? { chapter: liveResource.chapter } : {}),
            ...(liveResource.roleId ? { roleId: liveResource.roleId } : {}),
            currency: liveResource.currency,
            lcrCents: liveResource.lcrCents,
            ucrCents: liveResource.ucrCents,
            attributes: liveResource.dynamicFields ?? {},
          };
        }

        return {
          ...(existingSnapshot?.id ? { id: existingSnapshot.id } : {}),
          resourceId,
          ...(existingSnapshot?.sourceEid ? { sourceEid: existingSnapshot.sourceEid } : {}),
          displayName: existingSnapshot?.displayName ?? "Linked resource",
          ...(existingSnapshot?.chapter ? { chapter: existingSnapshot.chapter } : {}),
          ...(existingSnapshot?.roleId ? { roleId: existingSnapshot.roleId } : {}),
          currency: existingSnapshot?.currency ?? baseCurrency,
          lcrCents: existingSnapshot?.lcrCents ?? 0,
          ucrCents: existingSnapshot?.ucrCents ?? 0,
          ...(existingSnapshot?.fte != null ? { fte: existingSnapshot.fte } : {}),
          ...(existingSnapshot?.location ? { location: existingSnapshot.location } : {}),
          ...(existingSnapshot?.country ? { country: existingSnapshot.country } : {}),
          ...(existingSnapshot?.level ? { level: existingSnapshot.level } : {}),
          ...(existingSnapshot?.workType ? { workType: existingSnapshot.workType } : {}),
          attributes:
            typeof existingSnapshot?.attributes === "object" &&
            existingSnapshot.attributes !== null &&
            !Array.isArray(existingSnapshot.attributes)
              ? existingSnapshot.attributes
              : {},
        };
      })
      .filter((snapshot): snapshot is NonNullable<typeof snapshot> => snapshot !== null);

    await updateMutation.mutateAsync({
      id: estimate.id,
      ...(estimate.projectId ? { projectId: estimate.projectId } : {}),
      name: name.trim(),
      ...(opportunityId.trim() ? { opportunityId: opportunityId.trim() } : {}),
      baseCurrency: baseCurrency.trim() || "EUR",
      ...(versionLabel.trim() ? { versionLabel: versionLabel.trim() } : {}),
      ...(versionNotes.trim() ? { versionNotes: versionNotes.trim() } : {}),
      assumptions: sanitizedAssumptions,
      scopeItems: sanitizedScopeItems,
      demandLines: sanitizedDemandLines,
      resourceSnapshots: sanitizedResourceSnapshots,
      metrics: [],
    });
  }

  function renderOverviewEditor() {
    return (
      <div className="grid gap-6 xl:grid-cols-[minmax(0,1.1fr),340px]">
        <section className="space-y-5 rounded-3xl border border-gray-200 bg-white p-6 shadow-sm">
          <div className="grid gap-4 md:grid-cols-2">
            <label>
              <span className="app-label">Estimate name</span>
              <input
                className="app-input"
                value={name}
                onChange={(event) => setName(event.target.value)}
              />
            </label>
            <label>
              <span className="app-label">Opportunity ID</span>
              <input
                className="app-input"
                value={opportunityId}
                onChange={(event) => setOpportunityId(event.target.value)}
              />
            </label>
            <label>
              <span className="app-label">Base currency</span>
              <input
                className="app-input"
                maxLength={3}
                value={baseCurrency}
                onChange={(event) => setBaseCurrency(event.target.value.toUpperCase())}
              />
            </label>
            <label>
              <span className="app-label">Version label</span>
              <input
                className="app-input"
                value={versionLabel}
                onChange={(event) => setVersionLabel(event.target.value)}
              />
            </label>
          </div>

          <label className="block">
            <span className="app-label">Version notes</span>
            <textarea
              className="app-input min-h-32"
              value={versionNotes}
              onChange={(event) => setVersionNotes(event.target.value)}
            />
          </label>
        </section>

        <aside className="rounded-3xl border border-gray-200 bg-white p-5 shadow-sm">
          <p className="text-sm font-semibold text-gray-900">Live draft summary</p>
          <div className="mt-4 space-y-3">
            <div className="flex items-center justify-between rounded-2xl bg-gray-50 px-4 py-3">
              <span className="text-xs uppercase tracking-wide text-gray-400">Total Hours</span>
              <span className="text-sm font-semibold text-gray-900">
                {summary.totalHours.toFixed(1)}
              </span>
            </div>
            <div className="flex items-center justify-between rounded-2xl bg-gray-50 px-4 py-3">
              <span className="text-xs uppercase tracking-wide text-gray-400">Total Cost</span>
              <span className="text-sm font-semibold text-gray-900">
                {formatMoney(summary.totalCostCents, baseCurrency)}
              </span>
            </div>
            <div className="flex items-center justify-between rounded-2xl bg-gray-50 px-4 py-3">
              <span className="text-xs uppercase tracking-wide text-gray-400">Total Price</span>
              <span className="text-sm font-semibold text-gray-900">
                {formatMoney(summary.totalPriceCents, baseCurrency)}
              </span>
            </div>
          </div>
        </aside>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <div className="flex flex-wrap items-center justify-between gap-3 rounded-3xl border border-brand-200 bg-brand-50 px-5 py-4">
        <div>
          <p className="text-sm font-semibold text-brand-800">Editing working draft</p>
          <p className="text-sm text-brand-700">
            Changes overwrite the current working version and refresh summary metrics on save.
          </p>
        </div>
        <div className="flex gap-2">
          <button
            type="button"
            className="rounded-2xl border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700"
            onClick={onCancel}
          >
            Cancel
          </button>
          <button
            type="button"
            className="rounded-2xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white disabled:opacity-60"
            disabled={updateMutation.isPending}
            onClick={() => void handleSave()}
          >
            {updateMutation.isPending ? "Saving..." : "Save draft"}
          </button>
        </div>
      </div>

      {error && (
        <div className="rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
          {error}
        </div>
      )}

      {tab === "overview" && renderOverviewEditor()}
      {tab === "assumptions" && (
        <AssumptionEditor assumptions={assumptions} onChange={setAssumptions} />
      )}
      {tab === "scope" && (
        <ScopeItemEditor
          scopeItems={scopeItems}
          onChange={setScopeItems}
          scopeImportWarnings={scopeImportWarnings}
          onScopeImportWarnings={setScopeImportWarnings}
        />
      )}
      {tab === "staffing" && (
        <DemandLineEditor
          demandLines={demandLines}
          onChange={setDemandLines}
          resourceOptions={resourceOptions}
          resourceMap={resourceMap}
          snapshotByResourceId={snapshotByResourceId}
          baseCurrency={baseCurrency}
          projectStartDate={projectStartDate}
          projectEndDate={projectEndDate}
          spreadMonths={spreadMonths}
          aggregatedSpread={aggregatedSpread}
        />
      )}
      {(tab === "versions" || tab === "exports") && renderEditorHint()}
    </div>
  );
}
