import { NextResponse } from "next/server";
import { z } from "zod";
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
import { parseBooleanFromUnknown } from "@open-mercato/shared/lib/boolean";
import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
import { isTenantDataEncryptionEnabled } from "@open-mercato/shared/lib/encryption/toggles";
import { fetchStuckDealIds } from "../../../lib/stuckDeals.js";
import { findMatchingEntityIdsBySearchTokensAcrossSources } from "../../utils.js";
import { E } from "../../../../../generated/entities.ids.generated.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("customers");
const metadata = {
  GET: { requireAuth: true, requireFeatures: ["customers.deals.view"] }
};
const querySchema = z.object({
  pipelineId: z.string().uuid().optional(),
  search: z.string().optional(),
  status: z.array(z.enum(["open", "closed", "win", "loose"])).optional(),
  ownerUserId: z.array(z.string().uuid()).optional(),
  personId: z.array(z.string().uuid()).optional(),
  companyId: z.array(z.string().uuid()).optional(),
  isStuck: z.preprocess((value) => {
    const parsed = parseBooleanFromUnknown(value);
    return parsed === null ? value : parsed;
  }, z.boolean()).optional(),
  isOverdue: z.preprocess((value) => {
    const parsed = parseBooleanFromUnknown(value);
    return parsed === null ? value : parsed;
  }, z.boolean()).optional(),
  expectedCloseAtFrom: z.string().optional(),
  expectedCloseAtTo: z.string().optional()
});
const stageBreakdownByCurrencySchema = z.object({
  currency: z.string(),
  total: z.number(),
  count: z.number()
});
const stageAggregateSchema = z.object({
  stageId: z.string(),
  count: z.number(),
  openCount: z.number(),
  totalInBaseCurrency: z.number(),
  byCurrency: z.array(stageBreakdownByCurrencySchema),
  convertedAll: z.boolean(),
  missingRateCurrencies: z.array(z.string())
});
const aggregateResponseSchema = z.object({
  baseCurrencyCode: z.string().nullable(),
  perStage: z.array(stageAggregateSchema)
});
const aggregateErrorSchema = z.object({
  error: z.string()
});
const openApi = {
  tag: "Customers",
  summary: "Deals aggregate per pipeline stage",
  methods: {
    GET: {
      summary: "Per-stage counts and currency totals for kanban lane headers",
      description: "Returns per-stage counts and totals for deals, with values converted to the tenant base currency where rates are available. Used to power kanban lane headers without loading every deal.",
      query: querySchema,
      responses: [
        { status: 200, description: "Per-stage aggregate payload", schema: aggregateResponseSchema }
      ],
      errors: [
        { status: 400, description: "Invalid query parameters", schema: aggregateErrorSchema },
        { status: 401, description: "Unauthorized", schema: aggregateErrorSchema }
      ]
    }
  }
};
function readArrayParam(searchParams, key) {
  const all = searchParams.getAll(key);
  if (!all.length) return null;
  const flat = all.flatMap((v) => v.split(","));
  const trimmed = flat.map((s) => s.trim()).filter(Boolean);
  return trimmed.length ? trimmed : null;
}
function restrictToIds(where, values, ids) {
  if (ids.length === 0) {
    where.push("id = ?");
    values.push("00000000-0000-0000-0000-000000000000");
    return;
  }
  const placeholders = ids.map(() => "?").join(",");
  where.push(`id IN (${placeholders})`);
  values.push(...ids);
}
async function GET(req) {
  const auth = await getAuthFromRequest(req);
  if (!auth?.tenantId || !auth.orgId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  const url = new URL(req.url);
  const params = url.searchParams;
  const parsed = querySchema.safeParse({
    pipelineId: params.get("pipelineId") ?? void 0,
    search: params.get("search") ?? void 0,
    status: readArrayParam(params, "status") ?? void 0,
    ownerUserId: readArrayParam(params, "ownerUserId") ?? void 0,
    personId: readArrayParam(params, "personId") ?? void 0,
    companyId: readArrayParam(params, "companyId") ?? void 0,
    isStuck: params.get("isStuck") ?? void 0,
    isOverdue: params.get("isOverdue") ?? void 0,
    expectedCloseAtFrom: params.get("expectedCloseAtFrom") ?? void 0,
    expectedCloseAtTo: params.get("expectedCloseAtTo") ?? void 0
  });
  if (!parsed.success) {
    return NextResponse.json({ error: "Invalid query parameters" }, { status: 400 });
  }
  const container = await createRequestContainer();
  const em = container.resolve("em");
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
  const effectiveTenantId = scope.tenantId ?? auth.tenantId;
  const orgFilterIds = Array.isArray(scope.filterIds) && scope.filterIds.length > 0 ? scope.filterIds.filter((id) => typeof id === "string" && id.length > 0) : auth.orgId ? [auth.orgId] : [];
  if (!effectiveTenantId || orgFilterIds.length === 0) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  const baseCurrency = await em.getConnection().execute(
    `SELECT code FROM currencies WHERE tenant_id = ? AND organization_id = ? AND is_base = true AND deleted_at IS NULL LIMIT 1`,
    [effectiveTenantId, orgFilterIds[0]]
  );
  const baseCurrencyCode = baseCurrency[0]?.code ?? null;
  const orgPlaceholders = orgFilterIds.map(() => "?").join(",");
  const where = [
    "tenant_id = ?",
    `organization_id IN (${orgPlaceholders})`,
    "deleted_at IS NULL"
  ];
  const values = [effectiveTenantId, ...orgFilterIds];
  if (parsed.data.pipelineId) {
    where.push("pipeline_id = ?");
    values.push(parsed.data.pipelineId);
  }
  const search = parsed.data.search?.trim();
  if (search) {
    const searchCtx = {
      container,
      auth,
      organizationScope: scope,
      selectedOrganizationId: orgFilterIds[0],
      organizationIds: orgFilterIds,
      request: req
    };
    const matchingIds = await findMatchingEntityIdsBySearchTokensAcrossSources({
      ctx: searchCtx,
      query: search,
      sources: [
        {
          entityType: E.customers.customer_deal,
          fields: [
            "title",
            "description",
            "status",
            "pipeline_stage",
            "source",
            "value_amount",
            "value_currency",
            "cf:competitive_risk",
            "cf:implementation_complexity"
          ]
        }
      ]
    });
    if (matchingIds !== null && matchingIds.length > 0) {
      restrictToIds(where, values, matchingIds);
    } else if (isTenantDataEncryptionEnabled()) {
      restrictToIds(where, values, []);
    } else {
      const searchPattern = `%${escapeLikePattern(search)}%`;
      where.push("(title ILIKE ? ESCAPE '\\' OR description ILIKE ? ESCAPE '\\')");
      values.push(searchPattern, searchPattern);
    }
  }
  if (parsed.data.status && parsed.data.status.length) {
    const placeholders = parsed.data.status.map(() => "?").join(",");
    where.push(`status IN (${placeholders})`);
    values.push(...parsed.data.status);
  }
  if (parsed.data.ownerUserId && parsed.data.ownerUserId.length) {
    const placeholders = parsed.data.ownerUserId.map(() => "?").join(",");
    where.push(`owner_user_id IN (${placeholders})`);
    values.push(...parsed.data.ownerUserId);
  }
  if (parsed.data.expectedCloseAtFrom) {
    where.push("expected_close_at >= ?");
    values.push(parsed.data.expectedCloseAtFrom);
  }
  if (parsed.data.expectedCloseAtTo) {
    where.push("expected_close_at <= ?");
    values.push(parsed.data.expectedCloseAtTo);
  }
  if (parsed.data.isOverdue) {
    where.push("expected_close_at < CURRENT_DATE AND status = 'open'");
  }
  if (parsed.data.isStuck) {
    const stuckIds = await fetchStuckDealIds(em, orgFilterIds[0], effectiveTenantId);
    restrictToIds(where, values, stuckIds);
  }
  if (parsed.data.personId && parsed.data.personId.length) {
    const placeholders = parsed.data.personId.map(() => "?").join(",");
    where.push(`EXISTS (SELECT 1 FROM customer_deal_people dp WHERE dp.deal_id = customer_deals.id AND dp.person_entity_id IN (${placeholders}))`);
    values.push(...parsed.data.personId);
  }
  if (parsed.data.companyId && parsed.data.companyId.length) {
    const placeholders = parsed.data.companyId.map(() => "?").join(",");
    where.push(`EXISTS (SELECT 1 FROM customer_deal_companies dc WHERE dc.deal_id = customer_deals.id AND dc.company_entity_id IN (${placeholders}))`);
    values.push(...parsed.data.companyId);
  }
  const whereSql = where.join(" AND ");
  const rows = await em.getConnection().execute(
    `SELECT
        pipeline_stage_id AS stage_id,
        UPPER(COALESCE(value_currency, '')) AS currency,
        COALESCE(SUM(value_amount), 0) AS total,
        COUNT(*) AS count,
        COUNT(*) FILTER (WHERE status = 'open') AS open_count
      FROM customer_deals
      WHERE ${whereSql}
      GROUP BY pipeline_stage_id, UPPER(COALESCE(value_currency, ''))`,
    values
  );
  const stageMap = /* @__PURE__ */ new Map();
  for (const row of rows) {
    const stageId = row.stage_id ?? "__unassigned";
    const total = Number(row.total ?? 0);
    const count = Number(row.count ?? 0);
    const openCount = Number(row.open_count ?? 0);
    const currency = (row.currency ?? "").toString().trim();
    if (!stageMap.has(stageId)) {
      stageMap.set(stageId, {
        stageId,
        count: 0,
        openCount: 0,
        totalInBaseCurrency: 0,
        byCurrency: [],
        convertedAll: true,
        missingRateCurrencies: []
      });
    }
    const agg = stageMap.get(stageId);
    agg.count += count;
    agg.openCount += openCount;
    if (currency.length > 0) {
      agg.byCurrency.push({ currency, total, count });
    }
  }
  if (baseCurrencyCode) {
    const exchange = container.resolve("exchangeRateService");
    const today = /* @__PURE__ */ new Date();
    const distinctCurrencies = /* @__PURE__ */ new Set();
    for (const agg of stageMap.values()) {
      for (const row of agg.byCurrency) {
        if (row.currency && row.currency !== baseCurrencyCode) {
          distinctCurrencies.add(row.currency);
        }
      }
    }
    const rateCache = /* @__PURE__ */ new Map();
    if (exchange && distinctCurrencies.size > 0) {
      const pairs = Array.from(distinctCurrencies).map((c) => ({
        fromCurrencyCode: c,
        toCurrencyCode: baseCurrencyCode
      }));
      try {
        const results = await exchange.getRates({
          pairs,
          date: today,
          scope: { tenantId: effectiveTenantId, organizationId: orgFilterIds[0] },
          options: { maxDaysBack: 60, autoFetch: false }
        });
        for (const [key, rateResult] of results) {
          if (rateResult.rates.length > 0) {
            const rate = Number(rateResult.rates[0].rate);
            if (Number.isFinite(rate) && rate > 0) {
              rateCache.set(key, rate);
            }
          }
        }
      } catch (err) {
        logger.warn("exchange-rate lookup failed; falling back to per-currency totals", { component: "deals.aggregate", err });
      }
    }
    for (const agg of stageMap.values()) {
      let totalBase = 0;
      let convertedAll = true;
      const missingRateCurrencies = [];
      for (const row of agg.byCurrency) {
        if (!row.currency) continue;
        if (row.currency === baseCurrencyCode) {
          totalBase += row.total;
          continue;
        }
        const key = `${row.currency}/${baseCurrencyCode}`;
        const rate = rateCache.get(key);
        if (rate !== void 0) {
          totalBase += row.total * rate;
        } else {
          convertedAll = false;
          if (!missingRateCurrencies.includes(row.currency)) {
            missingRateCurrencies.push(row.currency);
          }
        }
      }
      agg.totalInBaseCurrency = Math.round(totalBase);
      agg.convertedAll = convertedAll;
      agg.missingRateCurrencies = missingRateCurrencies;
    }
  } else {
    for (const agg of stageMap.values()) {
      const present = agg.byCurrency.map((row) => row.currency).filter((c) => !!c);
      if (present.length > 0) {
        agg.convertedAll = false;
        agg.missingRateCurrencies = Array.from(new Set(present));
      }
    }
  }
  for (const agg of stageMap.values()) {
    agg.byCurrency.sort((a, b) => b.total - a.total);
  }
  const response = {
    baseCurrencyCode,
    perStage: Array.from(stageMap.values())
  };
  return NextResponse.json(response);
}
export {
  GET,
  metadata,
  openApi
};
//# sourceMappingURL=route.js.map
