import { describe, expect, it } from "vitest";

import {
  filterFixtureFacilityPerformance,
  filterFixtureReportKpis,
  filterFixtureReportRows,
} from "@/lib/db/fixture-reports";
import { facilityPerformance, reportKpis } from "@/lib/fixtures";
import { REPORT_KPI_CUSTOMER_ID } from "@/lib/fixtures/reports";
import { todayJst } from "@/lib/format/date";
import { shiftPeriod } from "@/lib/format/period";
import {
  selectFacilityPerformanceRows,
  selectKpiRow,
  type ReportQuery,
} from "@/lib/reports/query";
import type {
  FacilityPerformanceRow,
  ReportDbQuery,
  ReportKpiDbQuery,
  ReportKpiRow,
} from "@/lib/types";

/**
 * fixtures 版レポート4メソッドの絞り込み（指示書AC ★段階5★）。
 *
 * ★★何を守るためのテストか★★
 *   1. ★`USE_FIXTURES` の true/false で件数が変わらないこと★（禁止事項6）—
 *      Prisma 版が SQL に降ろした条件と ★同じ意味★ をここで再現している
 *   2. ★★`facility_ids` の `null` と `[]` が別物であること★★（★罠1 / 罠9★）—
 *      ★`[]` を「条件なし」と読んで全件返す実装になっていないか★
 *   3. ★絞り込み規則の「正」（`selectXxxRows()` / `selectKpiRow()`）と一致すること★（C-5）
 */

function dbQuery(partial: Partial<ReportDbQuery> = {}): ReportDbQuery {
  // ★`facility_ids: null` = 施設で絞らない★（指示書AK 段階4b で `ReportDbQuery` に足った。
  //   ★`[]`（0件ヒット）と混同しないこと★ — 既定を `[]` にすると全テストが0件になる）
  return {
    period: "weekly",
    year: 2026,
    month: 9,
    week: 2,
    facility_ids: null,
    ...partial,
  };
}

/**
 * `reportKpis()` 用（指示書AK ★段階6★）。★`customer_id` は必須★ —
 * ★「顧客が決まらない」状態でデータ層を呼ばない契約★（`ReportKpiDbQuery`）。
 * ★既定は fixtures のKPI行が属する屋号1★。
 */
function kpiDbQuery(partial: Partial<ReportKpiDbQuery> = {}): ReportKpiDbQuery {
  return { ...dbQuery(), customer_id: REPORT_KPI_CUSTOMER_ID, ...partial };
}

/** `selectXxxRows()`（絞り込み規則の「正」）に渡す形。期間以外は素通しの値にする */
function memoryQuery(db: ReportDbQuery): ReportQuery {
  return {
    ...db,
    facilityName: "",
    serial: "",
    status: "all",
    // ★絞り込まない（指示書AK 段階2 で足った2つ。`null` = 条件なし）★
    groupId: null,
    customerId: null,
    limit: 100,
  };
}

const KPI_ROWS: ReportKpiRow[] = [
  { period: "weekly", year: 2026, month: 9, week: 2, kpi: reportKpis[0].kpi },
  { period: "weekly", year: 2026, month: 9, week: 1, kpi: reportKpis[0].kpi },
  // ★月次は week=0（D-072）。★週次の第0週ではない★ので weekly とは別行になる★
  { period: "monthly", year: 2026, month: 9, week: 0, kpi: reportKpis[0].kpi },
  { period: "monthly", year: 2026, month: 8, week: 0, kpi: reportKpis[0].kpi },
];

describe("filterFixtureReportRows（期間キーで絞る）", () => {
  it("要求された期間の行だけを返す", () => {
    expect(filterFixtureReportRows(KPI_ROWS, dbQuery())).toEqual([KPI_ROWS[0]]);
  });

  it("★前期（1つ前の期間）の行は返さない★（Prisma 版は前期比のために読むが、返しはしない）", () => {
    const rows = filterFixtureReportRows(KPI_ROWS, dbQuery());
    expect(rows.some((row) => row.week === 1)).toBe(false);
  });

  it("★monthly と weekly は混ざらない★（monthly の week=0 は予約値。D-072）", () => {
    expect(
      filterFixtureReportRows(KPI_ROWS, dbQuery({ period: "monthly", week: 0 }))
    ).toEqual([KPI_ROWS[2]]);
  });

  it("該当が無ければ空配列（404 にしない。指示書AC 段階5 期待値表 #5）", () => {
    expect(filterFixtureReportRows(KPI_ROWS, dbQuery({ year: 2020 }))).toEqual([]);
  });
});

describe("filterFixtureFacilityPerformance（期間キー ＋ 施設ID）", () => {
  const ROWS: FacilityPerformanceRow[] = [
    { period: "weekly", year: 2026, month: 9, week: 2, item: { ...facilityPerformance[0].item, facility_id: 1 } },
    { period: "weekly", year: 2026, month: 9, week: 2, item: { ...facilityPerformance[0].item, facility_id: 2 } },
    { period: "weekly", year: 2026, month: 9, week: 1, item: { ...facilityPerformance[0].item, facility_id: 1 } },
  ];

  it("★`facility_ids: null` → 施設で絞らない★", () => {
    expect(
      filterFixtureFacilityPerformance(ROWS, { ...dbQuery(), facility_ids: null })
    ).toHaveLength(2);
  });

  it("★★`facility_ids: []` → 0件★★（★罠9。「条件なし」と読んで全件返していないか★）", () => {
    expect(
      filterFixtureFacilityPerformance(ROWS, { ...dbQuery(), facility_ids: [] })
    ).toEqual([]);
  });

  it("指定した施設だけを返す", () => {
    const rows = filterFixtureFacilityPerformance(ROWS, {
      ...dbQuery(),
      facility_ids: [2],
    });
    expect(rows.map((row) => row.item.facility_id)).toEqual([2]);
  });

  it("★期間の絞り込みと AND になる★（同じ施設でも期間が違えば落ちる）", () => {
    const rows = filterFixtureFacilityPerformance(ROWS, {
      ...dbQuery({ week: 1 }),
      facility_ids: [1],
    });
    expect(rows).toHaveLength(1);
    expect(rows[0].week).toBe(1);
  });
});

describe("★絞り込み規則の「正」と一致する★（指示書AC C-5。fixtures の実データで突き合わせる）", () => {
  it("facilityPerformance: fixtures 版の絞り込み ＝ `selectFacilityPerformanceRows()`", () => {
    for (const row of facilityPerformance) {
      const q = dbQuery({
        period: row.period,
        year: row.year,
        month: row.month,
        week: row.week,
      });
      expect(
        filterFixtureFacilityPerformance(facilityPerformance, {
          ...q,
          facility_ids: null,
        }).map((r) => r.item)
      ).toEqual(selectFacilityPerformanceRows(facilityPerformance, memoryQuery(q)));
    }
  });

  it("reportKpis: fixtures 版の絞り込み ＝ `selectKpiRow()`", () => {
    for (const row of reportKpis) {
      const q = kpiDbQuery({
        period: row.period,
        year: row.year,
        month: row.month,
        week: row.week,
      });
      const filtered = filterFixtureReportKpis(reportKpis, q);
      expect(selectKpiRow(filtered, memoryQuery(q))).toEqual(
        selectKpiRow(reportKpis, memoryQuery(q))
      );
    }
  });

  /**
   * ★年次（`month=0, week=0`）は月次（`week=0`）と別キー★（D-151。CLAUDE.md §4.7）
   *
   * ★何を守るためのテストか★: fixtures に今年・前年の行が無く、`USE_FIXTURES=true` でだけ
   *   年次のKPIが4つとも null になっていた（監督の実測 2026-10-01。禁止事項6）。
   *   月次の行も `week=0` を持つので、★年次の要求に月次の行が当たらない／その逆も無い★ことを
   *   fixtures の実データで確かめる。
   */
  it("★年次の要求には年次の行だけが当たり、月次の week=0 行と取り違えない（今年・前年とも KPI が出る）★", () => {
    const thisYear = Number(todayJst().slice(0, 4));
    const lastYear = shiftPeriod("yearly", thisYear, 0, 0, -1);
    const monthlyRows = reportKpis.filter((row) => row.period === "monthly");
    // 前提: 取り違えの相手になる月次（week=0）の行が fixtures に実在する
    expect(monthlyRows.length).toBeGreaterThan(0);
    monthlyRows.forEach((row) => expect(row.week).toBe(0));

    for (const year of [thisYear, lastYear.year]) {
      const q = kpiDbQuery({ period: "yearly", year, month: 0, week: 0 });
      const filtered = filterFixtureReportKpis(reportKpis, q);
      expect(filtered, `${year}年`).toHaveLength(1);
      expect(filtered[0]).toMatchObject({ period: "yearly", year, month: 0, week: 0 });
      // ★監督の実測で null だった completion_rate が数値で出る★
      const kpi = selectKpiRow(filtered, memoryQuery(q));
      expect(kpi, `${year}年`).not.toBeNull();
      expect(typeof kpi?.completion_rate.value, `${year}年`).toBe("number");
    }

    // 逆向き: 月次の要求（week=0）に年次の行は混ざらない
    for (const row of monthlyRows) {
      const q = kpiDbQuery({ period: "monthly", year: row.year, month: row.month, week: 0 });
      expect(filterFixtureReportKpis(reportKpis, q).map((r) => r.period)).toEqual(["monthly"]);
    }
  });
});

/**
 * `filterFixtureReportKpis`（期間キー ＋ ★対象顧客★。指示書AK ★段階6★）。
 *
 * ★★何を守るためのテストか★★
 *  - ★Prisma 版が `customer_summaries` の `where` に降ろした `customer_id` と
 *    ★★同じ意味★★ を fixtures 側でも再現していること★（禁止事項6。
 *    ★`USE_FIXTURES` の切替で数字が変わってはいけない★）
 *  - ★★別顧客の行を拾わないこと★★（★拾うと「別会社の数字で判断する」ことになる★。D-5）
 */
describe("filterFixtureReportKpis（期間キー ＋ 対象顧客）", () => {
  /** ★2社ぶんの行★（fixtures 本体は1社ぶんしか持たないのでここで組み立てる） */
  const ROWS = [
    { period: "weekly", year: 2026, month: 9, week: 2, customer_id: 1, kpi: reportKpis[0].kpi },
    { period: "weekly", year: 2026, month: 9, week: 2, customer_id: 2, kpi: reportKpis[1].kpi },
    { period: "weekly", year: 2026, month: 9, week: 1, customer_id: 2, kpi: reportKpis[1].kpi },
  ] as const;

  it("★対象顧客の行だけを返す★（★期待値表 6-2 の fixtures 版★）", () => {
    expect(filterFixtureReportKpis(ROWS, kpiDbQuery({ customer_id: 1 }))).toEqual([ROWS[0]]);
    expect(filterFixtureReportKpis(ROWS, kpiDbQuery({ customer_id: 2 }))).toEqual([ROWS[1]]);
  });

  it("★存在しない顧客 → 空配列★（404 にしない。期待値表 6-9）", () => {
    expect(filterFixtureReportKpis(ROWS, kpiDbQuery({ customer_id: 999 }))).toEqual([]);
  });

  it("★期間が違えば同じ顧客でも返さない★（期間キーの絞り込みを壊していない）", () => {
    expect(filterFixtureReportKpis(ROWS, kpiDbQuery({ customer_id: 1, week: 1 }))).toEqual([]);
  });

  it("★fixtures 本体のKPI行は屋号1 のもの★（`REPORT_KPI_CUSTOMER_ID`）", () => {
    for (const row of reportKpis) {
      expect(row.customer_id).toBe(REPORT_KPI_CUSTOMER_ID);
    }
  });
});
