import { todayJst } from "@/lib/format/date";
import { shiftPeriod, weekOfMonth } from "@/lib/format/period";
import { toKpiValue } from "@/lib/judge";
import type {
  ContinuousProblem,
  ContinuousProblemRow,
  ErrorBreakdownItem,
  ErrorBreakdownRow,
  FacilityPerformance,
  FacilityPerformanceRow,
  ReportKpi,
  ReportKpiRow,
  ReportPeriodKey,
} from "@/lib/types";
import { dateOnly } from "./_helpers";

/**
 * レポートのサンプルデータ
 *
 * docs/fixtures.md §4 のケースを網羅。
 *   - 3ステータス（良好 / 注意 / 要改善）が最低1件ずつ
 *   - 前期比 ＋（改善 ▲）/ −（悪化 ▼）
 *   - 前期データなし（prev が null。新規施設）
 *   - 11件以上（「全X施設を表示」「他N件を表示」の発火確認。docs/fixtures.md §10）
 *   - エラー種別（生値。未知の値・空文字を含む）
 *   - 連続問題施設 L1 / L2 / L3 が最低1件ずつ
 *   - 当期データ（進行中の週・月・年。サマリテーブルに存在しない前提のケース）
 *   - 週次 / 月次 / 年次 の3モード（年次は D-151 で解禁）
 *   - 空配列（該当0件のときの表示。docs/fixtures.md §10）
 *
 * ★ 率はすべて丸める前の生値。表示側で整数に丸めること（CLAUDE.md 4.3）★
 *
 * ★ KpiValue の組み立ては `lib/judge` の `toKpiValue()` に一本化している ★
 *   以前はこのファイルにローカルの `kpi()` があり、`diff` を小数第1位で丸めていたため、
 *   同じ `KpiValue` を作る実装が2つあって `diff` の値が食い違っていた。
 *   判定・計算ロジックは USE_FIXTURES の切替の外側に置く（D-044）。
 */

// ---------------------------------------------------------------- 期間キー
//
// `DataSource` は「引数なしで全件返す」契約なので、行データ自身が
// 「どの期間のものか」を持っていないと週次/月次/年次/当期を出し分けられない
// （`ReportPeriodKey` のコメント参照）。
//
// fixtures は実行日からの相対で期間キーを作る（`_helpers.ts` の日付と同じ方針）。
// 日付を固定すると、時間が経つにつれ「当期」が存在しないデータになり、
// 当期/確定期の出し分けが画面から確認できなくなるため。

const today = todayJst();
const thisWeek = weekOfMonth(today);
const lastWeek = shiftPeriod(
  "weekly",
  thisWeek.year,
  thisWeek.month,
  thisWeek.week,
  -1
);
const thisMonth = {
  year: Number(today.slice(0, 4)),
  month: Number(today.slice(5, 7)),
};
// 月次は week=0（D-072。サマリ4テーブルの「月の集計値」と同じ表し方）
const lastMonth = shiftPeriod(
  "monthly",
  thisMonth.year,
  thisMonth.month,
  0,
  -1
);
// ★年次は month=0 / week=0（CLAUDE.md §4.7。`month=0 AND week=0` が年次。D-151）★
//   `shiftPeriod("yearly", …)` は month / week を無視し、0 を返す（`lib/format/period.ts`）
const thisYear = Number(today.slice(0, 4));
const lastYear = shiftPeriod("yearly", thisYear, 0, 0, -1);

/** 今週（進行中の週）。`isCurrentPeriod()` が true になる */
const currentWeekKey: ReportPeriodKey = {
  period: "weekly",
  year: thisWeek.year,
  month: thisWeek.month,
  week: thisWeek.week,
};

/** 先週（確定済みの週。サマリテーブル由来を想定） */
const lastWeekKey: ReportPeriodKey = {
  period: "weekly",
  year: lastWeek.year,
  month: lastWeek.month,
  week: lastWeek.week,
};

/** 今月（進行中の月）。★月次は week=0（D-072）★ */
const currentMonthKey: ReportPeriodKey = {
  period: "monthly",
  year: thisMonth.year,
  month: thisMonth.month,
  week: 0,
};

/** 先月（確定済みの月）。★月次は week=0（D-072）★ */
const lastMonthKey: ReportPeriodKey = {
  period: "monthly",
  year: lastMonth.year,
  month: lastMonth.month,
  week: 0,
};

/**
 * 今年（進行中の年）。★年次は month=0 / week=0（CLAUDE.md §4.7）★
 * ★先月・今月の `week: 0` 行とは `period` と `month` が違うので別キーになる★
 */
const currentYearKey: ReportPeriodKey = {
  period: "yearly",
  year: thisYear,
  month: 0,
  week: 0,
};

/** 前年（確定済みの年。サマリテーブル由来を想定）。★年次は month=0 / week=0★ */
const lastYearKey: ReportPeriodKey = {
  period: "yearly",
  year: lastYear.year,
  month: lastYear.month,
  week: lastYear.week,
};

/**
 * fixtures が用意する6期間（今週 / 先週 / 今月 / 先月 / 今年 / 前年）。
 *
 * ★今年・前年は D-151（年次の解禁）で足した★ — 無いと `USE_FIXTURES=true` でだけ
 *   年次のKPI・一覧が空になり、Prisma 版と挙動が変わる（`CLAUDE.md` §9.5 / 禁止事項6）。
 *
 * 施設別パフォーマンス・連続問題施設・エラー種別内訳は、この6期間に
 * **同じ配列データを使い回して**紐付ける。期間ごとに実データが変わる作りは
 * `prismaSource` 実装時の課題であり、fixtures は「期間ナビで送っても
 * 画面が壊れず描画される」ことを確認できれば足りるため。
 */
const PERIOD_KEYS: readonly ReportPeriodKey[] = [
  currentWeekKey,
  lastWeekKey,
  currentMonthKey,
  lastMonthKey,
  currentYearKey,
  lastYearKey,
];

/** 同じ item 配列を6期間ぶん複製して行データにする */
function forEachPeriod<T>(
  items: readonly T[]
): Array<ReportPeriodKey & { item: T }> {
  return PERIOD_KEYS.flatMap((key) =>
    items.map((item) => ({ ...key, item }))
  );
}

// ---------------------------------------------------------------- KPI 4指標

/** 確定期間（サマリテーブル由来を想定）。先週の値 */
export const kpiWeekly: ReportKpi = {
  operation_rate: toKpiValue(91.4, 89.6),
  launch_rate: toKpiValue(93.8, 94.2),
  completion_rate: toKpiValue(87.1, 84.9),
  error_rate: toKpiValue(4.6, 5.8),
};

/** 確定期間。先月の値 */
export const kpiMonthly: ReportKpi = {
  operation_rate: toKpiValue(89.7, 90.3),
  launch_rate: toKpiValue(92.5, 91.1),
  completion_rate: toKpiValue(85.4, 85.0),
  error_rate: toKpiValue(5.2, 4.8),
};

/**
 * 確定期間。前年の値（D-151 で年次を解禁したときに足した）。
 *
 * ★作り値★ — `kpiWeekly` / `kpiMonthly` と同じく実データ由来ではない（docs/fixtures.md §4）。
 *   「年次を選ぶと確定期間のKPIが出る」ことを画面で確かめられれば足りる。
 */
export const kpiYearly: ReportKpi = {
  operation_rate: toKpiValue(90.6, 88.9),
  launch_rate: toKpiValue(92.8, 91.5),
  completion_rate: toKpiValue(84.7, 83.2),
  error_rate: toKpiValue(5.3, 6.0),
};

/**
 * 当期（進行中の週・月・年）のケース。
 * サマリテーブルに存在しない前提で、DFA側APIが詳細テーブルから集計する（D-023）。
 * 前期比は比較対象があるので prev は入る。
 *
 * ★今週・今月・今年の3つに同じ値を使い回している★
 * 当期は「器だけ」作る段階（指示書F 先に決まっていること2）で、実際の集計は
 * `prismaSource` 実装時に入れるため、fixtures では週/月/年で値を作り分けない。
 */
export const kpiCurrentPeriod: ReportKpi = {
  operation_rate: toKpiValue(88.2, 91.4),
  launch_rate: toKpiValue(90.0, 93.8),
  completion_rate: toKpiValue(82.6, 87.1),
  error_rate: toKpiValue(6.1, 4.6),
};

/**
 * ★fixtures のKPIは「屋号1（`lib/fixtures/customers.ts` の id=1）の数字」として扱う★
 * （指示書AK ★段階6★）。
 *
 * ★★理由★★: 確定期間のKPIは `customer_summaries`（★顧客単位★）由来で、
 *   ★★複数顧客ぶんを合算する材料がDBに無い★★（§2-1）。
 *   ★「どの顧客の数字か」を持たない行を置くと、`customer_id` で絞れているかを
 *   fixtures 側で確かめられない★（＝`USE_FIXTURES` の切替で挙動が変わる。禁止事項6）。
 *
 * ★★屋号2〜4 のKPI行は**作らない**★★ — ★数字を捏造しないため★（`CLAUDE.md` §7）。
 *   ★屋号2〜4 を選ぶと 200・空のKPI★になる（＝「その顧客のサマリがまだ無い」。6-9 と同じ形）。
 */
export const REPORT_KPI_CUSTOMER_ID = 1;

/**
 * `DataSource.reportKpis()` が返す行データ。期間キーで1行が特定される。
 *
 * ★`customer_id` は `ReportKpiRow`（＝データ層が返す行型）には無い★ —
 *   ★fixtures 側の絞り込み（`filterFixtureReportKpis()`）が見るためだけの列★で、
 *   ★API レスポンスには出ない★（ルートは `row.kpi` しか読まない）。
 */
export const reportKpis: (ReportKpiRow & { customer_id: number })[] = [
  { ...currentWeekKey, customer_id: REPORT_KPI_CUSTOMER_ID, kpi: kpiCurrentPeriod },
  { ...lastWeekKey, customer_id: REPORT_KPI_CUSTOMER_ID, kpi: kpiWeekly },
  { ...currentMonthKey, customer_id: REPORT_KPI_CUSTOMER_ID, kpi: kpiCurrentPeriod },
  { ...lastMonthKey, customer_id: REPORT_KPI_CUSTOMER_ID, kpi: kpiMonthly },
  { ...currentYearKey, customer_id: REPORT_KPI_CUSTOMER_ID, kpi: kpiCurrentPeriod },
  { ...lastYearKey, customer_id: REPORT_KPI_CUSTOMER_ID, kpi: kpiYearly },
];

// ---------------------------------------------------------------- 施設別パフォーマンス

/**
 * 稼働率ワースト順（昇順）。
 *
 * `serial_number` は `lib/fixtures/facilities.ts` / `schedules.ts` の
 * facility_id ↔ シリアルの対応をそのまま使っている（施設名で照合したとき食い違わないようにするため）。
 */
const facilityPerformanceItems: FacilityPerformance[] = [
  {
    facility_id: 9,
    facility_name: "メガバリューマート三ツ橋中央ショッピングセンター店",
    serial_number: "8111R5628060133",
    operation_rate: toKpiValue(68.0, 75.0),
    launch_rate: toKpiValue(72.0, 80.0),
    completion_rate: toKpiValue(55.0, 62.0),
    error_rate: toKpiValue(12.5, 9.0),
    status: "bad",
    consecutive_problem_days: 5,
  },
  {
    facility_id: 6,
    facility_name: "バリューマート南丘駅前店",
    serial_number: "8111R5628060077",
    operation_rate: toKpiValue(72.4, 70.1),
    // ★稼働率 ≤ 出発成功率（第11回 P-6 の不変条件）を守るため出発成功率の側を上げた★（旧 65.0 / 68.0。指示書AU §11 Q5）
    launch_rate: toKpiValue(75.0, 71.0),
    completion_rate: toKpiValue(63.2, 60.0),
    error_rate: toKpiValue(9.8, 10.2),
    status: "bad",
    consecutive_problem_days: 3,
  },
  {
    facility_id: 2,
    facility_name: "バリューマート北町店",
    serial_number: "8111R5628060022",
    operation_rate: toKpiValue(78.5, 82.0),
    launch_rate: toKpiValue(82.0, 85.0),
    completion_rate: toKpiValue(71.3, 78.0),
    error_rate: toKpiValue(7.2, 5.5),
    status: "warning",
    consecutive_problem_days: 1,
  },
  {
    facility_id: 4,
    facility_name: "バリューマート西通り店",
    serial_number: "8111R5628060044",
    operation_rate: toKpiValue(80.2, 79.4),
    launch_rate: toKpiValue(84.5, 83.0),
    completion_rate: toKpiValue(74.8, 72.1),
    error_rate: toKpiValue(6.9, 7.4),
    status: "warning",
    // continuousProblems の同施設(L3・3日)と一致させる
    consecutive_problem_days: 3,
  },
  {
    facility_id: 7,
    facility_name: "メガバリューマート緑野店",
    serial_number: "8111R5628060099",
    operation_rate: toKpiValue(85.3, 83.0),
    launch_rate: toKpiValue(90.0, 88.0),
    completion_rate: toKpiValue(80.1, 78.0),
    error_rate: toKpiValue(5.1, 5.8),
    status: "warning",
    consecutive_problem_days: 0,
  },
  {
    // 前期データなし（新規施設。差分表示なし）
    facility_id: 12,
    facility_name: "フレッシュタウン湖畔店",
    serial_number: "8111R5628060166",
    operation_rate: toKpiValue(86.0, null),
    launch_rate: toKpiValue(88.4, null),
    completion_rate: toKpiValue(81.0, null),
    error_rate: toKpiValue(4.9, null),
    status: "warning",
    consecutive_problem_days: 0,
  },
  {
    facility_id: 10,
    facility_name: "フレッシュタウン大和店",
    serial_number: "8111R5628060144",
    operation_rate: toKpiValue(90.1, 88.2),
    launch_rate: toKpiValue(92.0, 90.5),
    completion_rate: toKpiValue(85.5, 83.0),
    error_rate: toKpiValue(4.2, 4.9),
    status: "good",
    consecutive_problem_days: 0,
  },
  {
    facility_id: 8,
    facility_name: "メガバリューマート桜台店",
    serial_number: "8111R5628060122",
    operation_rate: toKpiValue(91.8, 90.0),
    launch_rate: toKpiValue(94.0, 92.5),
    completion_rate: toKpiValue(87.2, 85.4),
    error_rate: toKpiValue(3.6, 4.1),
    status: "good",
    consecutive_problem_days: 0,
  },
  {
    facility_id: 3,
    facility_name: "メガバリューマート東口店",
    serial_number: "8111R5628060033",
    operation_rate: toKpiValue(92.4, 90.0),
    launch_rate: toKpiValue(95.0, 93.0),
    completion_rate: toKpiValue(88.1, 85.0),
    error_rate: toKpiValue(3.2, 4.0),
    status: "good",
    // continuousProblems の同施設(L2・2日)と一致させる
    consecutive_problem_days: 2,
  },
  {
    facility_id: 5,
    facility_name: "メガバリューマート港南総本店",
    serial_number: "8111R5628060055",
    operation_rate: toKpiValue(94.6, 93.8),
    launch_rate: toKpiValue(96.2, 95.0),
    completion_rate: toKpiValue(90.4, 89.1),
    error_rate: toKpiValue(2.4, 2.9),
    status: "good",
    consecutive_problem_days: 0,
  },
  {
    facility_id: 11,
    facility_name: "バリューマート川下店",
    serial_number: "8111R5628060155",
    operation_rate: toKpiValue(95.2, 94.0),
    launch_rate: toKpiValue(97.0, 96.1),
    completion_rate: toKpiValue(92.0, 90.8),
    error_rate: toKpiValue(2.0, 2.4),
    status: "good",
    consecutive_problem_days: 0,
  },
  {
    // 12件目 — 「全X施設を表示」の発火確認用
    facility_id: 1,
    facility_name: "メガバリューマート中央本店",
    serial_number: "8111R5628060011",
    operation_rate: toKpiValue(96.3, 95.0),
    launch_rate: toKpiValue(98.0, 97.0),
    completion_rate: toKpiValue(94.1, 92.0),
    error_rate: toKpiValue(1.8, 2.1),
    status: "good",
    consecutive_problem_days: 0,
  },
];

/** `DataSource.facilityPerformance()` が返す行データ */
export const facilityPerformance: FacilityPerformanceRow[] =
  forEachPeriod(facilityPerformanceItems);

/** 該当0件のケース（絞り込みで1件も残らないときの表示確認。docs/fixtures.md §10） */
export const facilityPerformanceEmpty: FacilityPerformanceRow[] = [];

// ---------------------------------------------------------------- 連続問題施設

/**
 * 連続問題施設。
 *
 * ★12件ある（`limit` 既定10 → 「他N件を表示」の発火確認用。docs/fixtures.md §10）★
 * うち2日以上（L2 / L3）が11件。**L1 は1件だけ意図的に残してある**：
 * APIルート側の「2日以上で絞る」（指示書F 先に決まっていること6）が実際に効いているかを
 * 確認するためのデータなので、消さないこと。
 *
 * facility_id 13 以降は「レポート用のダミー施設」で `lib/fixtures/facilities.ts` には存在しない。
 * 施設マスタは12件しかなく、そのうち連続問題に相当するのは4件だけなので、
 * 11件以上という要求を満たすには追加が必要だった。
 * `/reports/continuous-problems` は施設マスタを引かない（連続日数と summary をそのまま出す）ため、
 * この不一致で画面が壊れることはない。
 */
const continuousProblemItems: ContinuousProblem[] = [
  {
    facility_id: 9,
    facility_name: "メガバリューマート三ツ橋中央ショッピングセンター店",
    consecutive_problem_days: 5,
    escalation_level: "L3",
    last_problem_on: dateOnly(1),
    summary: "完了率が5日連続で30%未満。マップ再作成またはオンサイト訪問を検討",
  },
  {
    facility_id: 6,
    facility_name: "バリューマート南丘駅前店",
    consecutive_problem_days: 3,
    escalation_level: "L3",
    last_problem_on: dateOnly(1),
    summary: "3日連続でオフライン。通信環境の確認が必要",
  },
  {
    facility_id: 4,
    facility_name: "バリューマート西通り店",
    consecutive_problem_days: 3,
    escalation_level: "L3",
    last_problem_on: dateOnly(1),
    summary: "スケジュール存在日ベースで3日連続。カレンダー上は5日間",
  },
  {
    facility_id: 3,
    facility_name: "メガバリューマート東口店",
    consecutive_problem_days: 2,
    escalation_level: "L2",
    last_problem_on: dateOnly(1),
    summary: "2日連続で完了率50%未満。現場責任者へのヒアリングを推奨",
  },
  {
    // ★L1（1日）。「2日以上で絞る」が効いていることの確認用。消さないこと★
    facility_id: 2,
    facility_name: "バリューマート北町店",
    consecutive_problem_days: 1,
    escalation_level: "L1",
    last_problem_on: dateOnly(1),
    summary: "ハンドルアップ未解消により出発失敗。現場へ連絡",
  },
  {
    facility_id: 13,
    facility_name: "メガバリューマート白岩台店",
    consecutive_problem_days: 6,
    escalation_level: "L3",
    last_problem_on: dateOnly(1),
    summary: "6日連続で出発失敗。機体交換の検討が必要",
  },
  {
    facility_id: 14,
    facility_name: "バリューマート東雲町店",
    consecutive_problem_days: 4,
    escalation_level: "L3",
    last_problem_on: dateOnly(1),
    summary: "4日連続で完了率が△以下。通路の障害物が常設化している可能性",
  },
  {
    facility_id: 15,
    facility_name: "メガバリューマート新開地店",
    consecutive_problem_days: 3,
    escalation_level: "L3",
    last_problem_on: dateOnly(2),
    summary: "3日連続でブラシエラー。消耗品交換とオンサイト訪問を検討",
  },
  {
    facility_id: 16,
    facility_name: "フレッシュタウン栗ケ丘店",
    consecutive_problem_days: 2,
    escalation_level: "L2",
    last_problem_on: dateOnly(1),
    summary: "2日連続で緊急停止。運用手順の再教育を推奨",
  },
  {
    facility_id: 17,
    facility_name: "バリューマート宮の森店",
    consecutive_problem_days: 2,
    escalation_level: "L2",
    last_problem_on: dateOnly(1),
    summary: "2日連続で30分前チェックが未解消のまま出発失敗",
  },
  {
    facility_id: 18,
    facility_name: "フレッシュタウン浜通り店",
    consecutive_problem_days: 2,
    escalation_level: "L2",
    last_problem_on: dateOnly(2),
    summary: "2日連続でLostLocation。マップの再作成を検討",
  },
  {
    facility_id: 19,
    facility_name: "バリューマート辻堂北口店",
    consecutive_problem_days: 2,
    escalation_level: "L2",
    last_problem_on: dateOnly(1),
    summary: "2日連続で充電不足により未実施。充電器の設置環境を確認",
  },
];

/** `DataSource.continuousProblems()` が返す行データ */
export const continuousProblems: ContinuousProblemRow[] =
  forEachPeriod(continuousProblemItems);

/** 該当0件のケース（連続問題施設が1件もない期間の表示確認。docs/fixtures.md §10） */
export const continuousProblemsEmpty: ContinuousProblemRow[] = [];

// ---------------------------------------------------------------- エラー種別内訳

/**
 * エラー種別（`robot_error_reports.error_type` の生値）の件数（★件数降順★）。
 *
 * ★変換しない（D-077）。`type` は生値をそのまま入れ、`label` にも同じ生値を入れる★
 *   （`label` は将来 TeamWorks から正式な表示名が来たときの受け皿。d-watch-api.yaml 参照）。
 * ★値は docs/handoff/analysis/error-type-mapping.md の24値の「原文」から選んでいる★
 *   （同ファイルの「7分類」列・「推定される意味」列は参考資料であり使わない。禁止事項1）。
 * ★境界値として「未知の値」と「空文字」を1件ずつ含む★（docs/fixtures.md 方針。段階5）。
 *   未知の値: ERROR_TONE のフォールバック確認用。空文字: 画面が落ちないことの確認用
 *   （label が空になり行が視覚的に潰れるのが現状の挙動。直すには実データの値が要る）。
 *
 * `rate` は「全エラー件数に対する割合(%)」で、丸める前の生値。
 * 合計件数を定数に置かず reduce で出しているのは、件数を1つ直したときに
 * `rate` の分母が置き去りになるのを防ぐため（合計は常に100%になる）。
 */
const errorCounts: ReadonlyArray<Omit<ErrorBreakdownItem, "rate">> = [
  { type: "EmergencyStop", label: "EmergencyStop", count: 18 },
  { type: "LostLocalization", label: "LostLocalization", count: 12 },
  { type: "RobotHardwareErrorToAdmin", label: "RobotHardwareErrorToAdmin", count: 9 },
  { type: "LowBatteryLevel", label: "LowBatteryLevel", count: 7 },
  { type: "CleanWaterEmpty", label: "CleanWaterEmpty", count: 5 },
  { type: "TrashFull", label: "TrashFull", count: 3 },
  { type: "CanNotReach", label: "CanNotReach", count: 2 },
  // ★同数(1件)の組。文字列昇順が守られていることの確認も兼ねる（"" < "UnknownErrorFromCloud"）★
  // ★空文字。画面が落ちないこと・ソートが落ちないことの確認用（直すには実データが要る）★
  { type: "", label: "", count: 1 },
  // ★未知の値（受領24値に無い）。ERROR_TONE のフォールバックが効くことの確認用★
  { type: "UnknownErrorFromCloud", label: "UnknownErrorFromCloud", count: 1 },
];

const errorTotal = errorCounts.reduce((sum, e) => sum + e.count, 0);

const errorBreakdownItems: ErrorBreakdownItem[] = errorCounts.map((e) => ({
  ...e,
  rate: (e.count / errorTotal) * 100,
}));

/** `DataSource.errorBreakdown()` が返す行データ */
export const errorBreakdown: ErrorBreakdownRow[] =
  forEachPeriod(errorBreakdownItems);

/**
 * 該当0件のケース（エラーが1件もない期間の表示確認）。
 *
 * item が0件なので、6期間ぶん複製しても行は1件も生まれない（= 空配列）。
 * `errorBreakdown` の代わりにこれを `DataSource` に返させると、
 * どの期間を選んでも「該当なし」の表示になる。
 */
export const errorBreakdownEmpty: ErrorBreakdownRow[] = [];
