/**
 * GET /clean-logs/export — 清掃詳細ログCSVエクスポート
 *
 * /clean-logs と同一のフィルタを受け付ける（`sort` / `order`・予定あり／予定外も同じ。CSV は画面と同じ並び順）。
 * page / per_page は無視。UTF-8 BOM付き、ヘッダ行あり。
 */
import { NextResponse } from "next/server";
import { db } from "@/lib/db";
// ★CSVの組み立ては lib/clean-logs/csv.ts★ — App Router の route.ts は
//   HTTPメソッド以外の export を想定しておらず、ここに置くとテストから呼べない
//   （「全列が csvCell() を通っているか」をテストで固定したいため。D-125）
import { buildCleanLogCsv } from "@/lib/clean-logs/csv";
import {
  filterCleanLogs,
  parseCleanLogQuery,
  toDbQueryDate,
} from "@/lib/clean-logs/query";
import { sortCleanLogs } from "@/lib/clean-logs/sort";
import { MAX_CSV_ROWS } from "@/lib/config";
import { intersectFacilityIds } from "@/lib/facilities/intersect-ids";
import { resolveFacilityIdsByMasters } from "@/lib/facilities/resolve-by-master";
import { resolveFacilityIdsByName } from "@/lib/facilities/resolve-by-name";
import { withRequestId } from "@/lib/logger";
import { logApiError } from "@/lib/logger/api";
import type { ApiError, CleanLogItem } from "@/lib/types";

const ROUTE = "/api/clean-logs/export";

export async function GET(request: Request) {
  return withRequestId(request, async () => {
    const { searchParams } = new URL(request.url);
    const parsed = parseCleanLogQuery(searchParams);
    if (!parsed.ok) {
      return NextResponse.json(parsed.error, { status: parsed.status });
    }

    let rows: CleanLogItem[];
    try {
      // ★`/clean-logs` と**同じ引数**を渡す（指示書AC 段階2 D-6）★ —
      //   ★★片方だけ絞ると、画面の一覧と CSV の中身が食い違います★★。
      //   `page` / `per_page` はここでも無視する（CSV は全件出す）。
      //   ★★分類（`group_id` / `customer_id`）の解決と積集合も一覧と同じ★★（指示書AK 段階4）—
      //   ★ここだけ分類を無視すると、画面で屋号を選んで出した CSV に他社の行が混ざります★。
      const [nameIds, masterIds] = await Promise.all([
        resolveFacilityIdsByName(parsed.query.facilityName),
        resolveFacilityIdsByMasters(parsed.query),
      ]);
      const facilityIds = intersectFacilityIds(nameIds, masterIds);
      rows = await db.cleanLogs({
        // ★CSV も一覧と同じく単一ID の指定はしない★（指示書AC 段階3）
        id: null,
        date_from: toDbQueryDate(parsed.query.dateFrom),
        date_to: toDbQueryDate(parsed.query.dateTo),
        facility_ids: facilityIds,
      });
    } catch (e) {
      const body: ApiError = {
        error: { code: "internal_error", message: "データの取得に失敗しました", details: null },
      };
      logApiError({ route: ROUTE, method: "GET", body, status: 500, err: e });
      return NextResponse.json(body, { status: 500 });
    }

    const filtered = filterCleanLogs(rows, parsed.query);

    // ★★行数の歯止め（ユーザー指示 2026-09-15 / 指示書AI 段階8）★★
    //
    // ★絞り込みを適用したあと、CSVを組み立てる**前**に数える★ —
    //   ★★組み立ててから長さで判断しないこと★★。25万行ぶんの文字列を作ってから
    //   捨てるのでは、★メモリを使い切る危険が残ったまま★になる。
    //
    // ★★500 ではなく 422★★ — 利用者の指定が上限を超えているという話であって、
    //   サーバの不具合ではない（`CLAUDE.md` §9.3）。
    //
    // ★上限は「含む」★ — ちょうど `MAX_CSV_ROWS` 行は出せる（`>` であって `>=` ではない）。
    //
    // ★画面（`DetailLogScreen`）も `meta.total` を見て同じ判断をしてボタンを止めるが、
    //   ★★こちらを消さないこと★★ — APIを直接叩く経路が残る。
    //   ★画面は「親切」、サーバは「歯止め」で役割が違う★。
    if (filtered.length > MAX_CSV_ROWS) {
      const body: ApiError = {
        error: {
          code: "validation_failed",
          message:
            `この期間はCSVで出力できません。対象が${filtered.length}件で、` +
            `上限の${MAX_CSV_ROWS}件を超えています。` +
            `期間を短くするか、店舗で絞り込んでください。`,
          // ★実数を入れる★ — 「あと何件減らせばよいか」が文言を読まずに分かる
          details: { total: filtered.length, max: MAX_CSV_ROWS },
        },
      };
      return NextResponse.json(body, { status: 422 });
    }

    // ★行数上限の判定の**後**に並べ替える（捨てる行を並べ替えない。入れ忘れると CSV だけ既定順）★
    const sorted = sortCleanLogs(filtered, parsed.query.sort, parsed.query.order);
    const csv = buildCleanLogCsv(sorted);
    // ★ここで生の searchParams を読んでよいのは、上の parseCleanLogQuery が
    //   `^\d{4}-\d{2}-\d{2}$`（`lib/clean-logs/query.ts` の parseDate）で既に弾いており、
    //   形式違いなら 422 で return 済みだから★。だから Content-Disposition に
    //   `"` や改行などの制御文字は入らない。
    //   ★この検証を緩めるなら、ここも一緒に直すこと★（検証する場所と使う場所が
    //   別の関数に分かれた暗黙の依存になっている）。
    //   `parsed.query` 側は Date に正規化済みで元の文字列を持たないため、生値を使う。
    const from = searchParams.get("date_from") ?? "";
    const to = searchParams.get("date_to") ?? "";
    const range = from && to ? `_${from}_${to}` : "";

    return new NextResponse(csv, {
      status: 200,
      headers: {
        "Content-Type": "text/csv; charset=utf-8",
        "Content-Disposition": `attachment; filename="clean-logs${range}.csv"`,
      },
    });
  });
}
