import { db } from "@/app/db/db";
import { and, eq } from "drizzle-orm";
import {
  dealMeasureApprovals,
  dealMeasureApprovalEvents,
} from "@/app/db/schema/approvals";
import { userDefinedDealMeasures } from "@/app/db/schema/user_defined_deal_measures";
import { MEASURE_NAMES, type MeasureName } from "@/app/lib/measureDocumentRequirements";
import { syncMeasuresFieldToHubSpot } from "@/app/lib/hubspot/dealSync";
import {
  INSTRUCTED_MEASURES_PROP,
  PROPOSED_MEASURES_PROP,
  APPROVED_MEASURES_PROP,
} from "@/app/lib/instructMeasure";
import { parseMeasures } from "@/app/lib/parseMeasures";
import { hubspotDealData } from "@/app/db/schema/crm/hubspot_deal_table";

export interface BulkInstructDeal {
  dealId: string;
  measureNames: string[];
}

export interface BulkInstructTxResult {
  instructedRowIds: bigint[];
  approvedMeasuresByDeal: Record<string, string[]>;
}

export type RunBulkInstructTx = (params: {
  deals: Array<{ dealId: string; measureNames: MeasureName[] }>;
  userId: bigint;
  notes: string | null;
}) => Promise<BulkInstructTxResult>;

export type SyncInstructedForDeals = (params: {
  approvedMeasuresByDeal: Record<string, string[]>;
}) => Promise<{ ok: true } | { ok: false; error: string }>;

export type BulkInstructDealsResult =
  | { ok: true; hubspotSync: "ok" | "failed"; hubspotError?: string }
  | { ok: false; error: string };

export interface BulkInstructDealsInput {
  deals: BulkInstructDeal[];
  userId: bigint;
  notes?: string;
  deps?: {
    runBulkInstructTx?: RunBulkInstructTx;
    syncInstructed?: SyncInstructedForDeals;
  };
}

function isMeasureName(value: string): value is MeasureName {
  return (MEASURE_NAMES as ReadonlyArray<string>).includes(value);
}

const defaultRunBulkInstructTx: RunBulkInstructTx = async ({ deals, userId, notes }) => {
  return db.transaction(async (tx) => {
    const instructedRowIds: bigint[] = [];
    const approvedMeasuresByDeal: Record<string, string[]> = {};

    for (const { dealId, measureNames } of deals) {
      for (const measureName of measureNames) {
        const inserted = await tx
          .insert(userDefinedDealMeasures)
          .values({ hubspotDealId: dealId, measureName, source: "instructed", createdByUserId: userId, notes })
          .returning({ id: userDefinedDealMeasures.id });

        const rowId = inserted[0]?.id;
        if (!rowId) throw new Error(`Failed to insert instructed measure row for deal ${dealId}`);
        instructedRowIds.push(rowId);

        await tx
          .insert(dealMeasureApprovals)
          .values({ hubspotDealId: dealId, measureName, isApproved: true, approvedBy: userId })
          .onConflictDoUpdate({
            target: [dealMeasureApprovals.hubspotDealId, dealMeasureApprovals.measureName],
            set: { isApproved: true, approvedBy: userId, approvedAt: new Date() },
          });

        await tx.insert(dealMeasureApprovalEvents).values({
          hubspotDealId: dealId,
          measureName,
          action: "approved",
          actedBy: userId,
        });
      }

      const approvalRows = await tx
        .select({ measureName: dealMeasureApprovals.measureName })
        .from(dealMeasureApprovals)
        .where(
          and(
            eq(dealMeasureApprovals.hubspotDealId, dealId),
            eq(dealMeasureApprovals.isApproved, true),
          ),
        );
      approvedMeasuresByDeal[dealId] = approvalRows.map((r) => r.measureName);
    }

    return { instructedRowIds, approvedMeasuresByDeal };
  });
};

const defaultSyncInstructed: SyncInstructedForDeals = async ({ approvedMeasuresByDeal }) => {
  for (const [dealId, measures] of Object.entries(approvedMeasuresByDeal)) {
    const instructedRows = await db
      .select({ measureName: userDefinedDealMeasures.measureName })
      .from(userDefinedDealMeasures)
      .where(
        and(
          eq(userDefinedDealMeasures.hubspotDealId, dealId),
          eq(userDefinedDealMeasures.source, "instructed"),
        ),
      );
    const allInstructed = instructedRows.map((r) => r.measureName);

    const dealRow = await db
      .select({ proposedMeasures: hubspotDealData.proposedMeasures })
      .from(hubspotDealData)
      .where(eq(hubspotDealData.dealId, dealId))
      .limit(1);
    const existing = parseMeasures(dealRow[0]?.proposedMeasures ?? null);
    const mergedProposed = [...new Set([...existing, ...allInstructed])];

    const r1 = await syncMeasuresFieldToHubSpot({ hubspotDealId: dealId, propName: INSTRUCTED_MEASURES_PROP, measureNames: allInstructed });
    if (!r1.ok) return { ok: false, error: r1.error };

    const r2 = await syncMeasuresFieldToHubSpot({ hubspotDealId: dealId, propName: PROPOSED_MEASURES_PROP, measureNames: mergedProposed });
    if (!r2.ok) return { ok: false, error: r2.error };

    const r3 = await syncMeasuresFieldToHubSpot({ hubspotDealId: dealId, propName: APPROVED_MEASURES_PROP, measureNames: measures });
    if (!r3.ok) return { ok: false, error: r3.error };
  }
  return { ok: true };
};

export async function bulkInstructDeals(
  input: BulkInstructDealsInput,
): Promise<BulkInstructDealsResult> {
  if (input.deals.length === 0) {
    return { ok: false, error: "deals must not be empty" };
  }

  for (const { measureNames } of input.deals) {
    if (measureNames.length === 0) {
      return { ok: false, error: "each deal must have at least one measureName" };
    }
    for (const name of measureNames) {
      if (!isMeasureName(name.trim())) {
        return { ok: false, error: `Unknown measure: ${name}` };
      }
    }
  }

  const validatedDeals = input.deals.map((d) => ({
    dealId: d.dealId,
    measureNames: d.measureNames.map((m) => m.trim() as MeasureName),
  }));

  const runTx = input.deps?.runBulkInstructTx ?? defaultRunBulkInstructTx;
  const syncInstructed = input.deps?.syncInstructed ?? defaultSyncInstructed;

  let txResult: BulkInstructTxResult;
  try {
    txResult = await runTx({ deals: validatedDeals, userId: input.userId, notes: input.notes ?? null });
  } catch (err) {
    const message = err instanceof Error ? err.message : "Bulk instruct transaction failed";
    console.error("[bulkInstructDeals] transaction failed", { error: err });
    return { ok: false, error: message };
  }

  const syncResult = await syncInstructed({ approvedMeasuresByDeal: txResult.approvedMeasuresByDeal });

  if (!syncResult.ok) {
    return { ok: true, hubspotSync: "failed", hubspotError: syncResult.error };
  }

  return { ok: true, hubspotSync: "ok" };
}
