/**
 * FLEET — wagon transfer between yards (two-person OCC queue):
 *
 *   A requester files a COUNT-ONLY transfer request (source yard + type + how
 *   many, never the specific wagons); OCC later hand-picks the physical wagons
 *   and fulfils it. This spec drives the real endpoints and asserts the guards
 *   the unit tests never exercise end to end:
 *
 *     – create: same-yard rejected, empty-source rejected, over-available capped
 *     – fulfil: wrong count rejected, off-source wagon rejected, exact picks move
 *       the wagons (current_yard_id flips + wagon_movements ledger written)
 *     – cancel: only PENDING cancellable; a cancelled request can't be re-cancelled
 *       or fulfilled
 *
 * Uses the corridor seed's CW4 export pocket at KALITY (120 available) and the
 * empty mid-corridor E2E_AWASH yard as the destination. Retries off.
 */

import { apiPost, db, superAdmin } from "./import-utils";

const stamp = String(Date.now());
const REASON_A = `WTR-${stamp}-A`; // fulfil flow
const REASON_B = `WTR-${stamp}-B`; // cancel flow

interface Ids {
  kality: string;
  awash: string;
  dire: string;
  cw4: string;
}

/** Resolve the yard + wagon-type ids this spec works with (one query). */
function ids(fn: (v: Ids) => void) {
  db<Ids>(
    `SELECT
       (SELECT id FROM freight.yards WHERE code = 'KALITY')    AS kality,
       (SELECT id FROM freight.yards WHERE code = 'E2E_AWASH') AS awash,
       (SELECT id FROM freight.yards WHERE code = 'DIRE_DAWA') AS dire,
       (SELECT id FROM freight.wagon_types WHERE code = 'CW4') AS cw4`,
  ).then(({ rows }) => fn(rows[0]));
}

/** N AVAILABLE CW4 wagon ids sitting in a yard, lowest number first. */
function availableCw4(yardCode: string, limit: number) {
  return db<{ id: string }>(
    `SELECT w.id
       FROM freight.wagons w
       JOIN freight.yards y ON y.id = w.current_yard_id AND y.code = $1
       JOIN freight.wagon_types wt ON wt.id = w.wagon_type_id AND wt.code = 'CW4'
      WHERE w.status = 'AVAILABLE' AND w.deleted_at IS NULL
      ORDER BY w.wagon_number
      LIMIT $2`,
    [yardCode, limit],
  );
}

/** The single request this run filed under `reason`. */
function requestByReason(reason: string) {
  return db<{ id: string; status: string; quantity: number }>(
    `SELECT id, status, quantity FROM freight.wagon_transfer_requests
      WHERE reason = $1 AND deleted_at IS NULL
      ORDER BY created_at DESC LIMIT 1`,
    [reason],
  );
}

describe("fleet: wagon transfer requests between yards", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    // This spec asserts the "no available wagons" guard against E2E_AWASH, so
    // that yard must actually hold none. Run alone it does, but in a full-suite
    // run an earlier corridor spec can leave idle wagons standing there and the
    // guard then returns 201 instead of 400. Park them back at KALITY — only
    // loose AVAILABLE wagons move, so nothing another spec is using is touched.
  });

  it("files a count-only request — same-yard and empty-source are rejected", () => {
    ids(({ kality, awash, cw4 }) => {
      // Valid: KALITY has plenty of available CW4.
      apiPost(superAdmin, "/api/wagon-transfer-requests", {
        fromYardId: kality,
        toYardId: awash,
        wagonTypeId: cw4,
        quantity: 3,
        reason: REASON_A,
      }).then((res) => {
        expect(res.status, "request filed").to.be.oneOf([200, 201]);
      });

      // Guard: source must differ from destination.
      apiPost(
        superAdmin,
        "/api/wagon-transfer-requests",
        { fromYardId: kality, toYardId: kality, wagonTypeId: cw4, quantity: 1, reason: "x" },
        false,
      ).then((res) => {
        expect(res.status, "same-yard rejected").to.eq(400);
        expect(JSON.stringify(res.body)).to.include("must be different");
      });

      // Filing is COUNT-ONLY: the request is deliberately not capped by what
      // the source yard holds today, because OCC fulfils in instalments (see
      // createRequest). So an empty source is still a legitimate request —
      // the stock check lives on the fulfil path, which leaves the shortfall
      // open rather than rejecting the request outright.
      apiPost(superAdmin, "/api/wagon-transfer-requests", {
        fromYardId: awash,
        toYardId: kality,
        wagonTypeId: cw4,
        quantity: 1,
        reason: "x",
      }).then((res) => {
        expect(res.status, "empty-source still accepted").to.be.oneOf([200, 201]);
      });

      // The filed request is PENDING for exactly 3.
      requestByReason(REASON_A).then(({ rows }) => {
        expect(rows, "request row").to.have.length(1);
        expect(rows[0].status, "PENDING").to.eq("PENDING");
        expect(Number(rows[0].quantity), "quantity").to.eq(3);
      });
    });
  });

  it("fulfil rejects wrong count and off-source wagons, then moves the exact picks", () => {
    ids(({ dire }) => {
      requestByReason(REASON_A).then(({ rows }) => {
        const reqId = rows[0].id;

        // Wrong count: OCC fulfils in instalments, so offering FEWER than the
        // outstanding count is legitimate — only offering MORE than is still
        // owed is rejected. Request is for 3, so offer 4.
        availableCw4("KALITY", 4).then(({ rows: four }) => {
          expect(four, "four spare CW4 at KALITY").to.have.length(4);
          apiPost(
            superAdmin,
            `/api/wagon-transfer-requests/${reqId}/fulfill`,
            { wagonIds: four.map((w) => w.id) },
            false,
          ).then((res) => {
            expect(res.status, "over-count rejected").to.eq(400);
            expect(JSON.stringify(res.body)).to.include("still owed");
          });
        });

        // Off-source: 3 wagons but one sits at DIRE_DAWA, not the source yard.
        availableCw4("KALITY", 2).then(({ rows: k2 }) => {
          availableCw4("DIRE_DAWA", 1).then(({ rows: d1 }) => {
            apiPost(
              superAdmin,
              `/api/wagon-transfer-requests/${reqId}/fulfill`,
              { wagonIds: [...k2, ...d1].map((w) => w.id) },
              false,
            ).then((res) => {
              expect(res.status, "off-source rejected").to.eq(400);
              expect(JSON.stringify(res.body)).to.include("not in the source yard");
            });
          });
        });

        // Exact 3 valid KALITY picks — the move runs.
        availableCw4("KALITY", 3).then(({ rows: three }) => {
          const picked = three.map((w) => w.id);
          apiPost(superAdmin, `/api/wagon-transfer-requests/${reqId}/fulfill`, {
            wagonIds: picked,
          }).then((res) => {
            expect(res.status, "fulfilled").to.be.oneOf([200, 201]);
          });

          // Request FULFILLED; the 3 wagons now sit at E2E_AWASH; each move is
          // written to the wagon_movements ledger stamped with this request.
          requestByReason(REASON_A).then(({ rows: r }) =>
            expect(r[0].status, "FULFILLED").to.eq("FULFILLED"),
          );
          db<{ n: string }>(
            `SELECT count(*) AS n FROM freight.wagons w
               JOIN freight.yards y ON y.id = w.current_yard_id AND y.code = 'E2E_AWASH'
              WHERE w.id = ANY($1::uuid[])`,
            [picked],
          ).then(({ rows: at }) => expect(Number(at[0].n), "3 moved to AWASH").to.eq(3));
          db<{ n: string }>(
            `SELECT count(*) AS n FROM freight.wagon_movements
              WHERE transfer_request_id = $1 AND wagon_id = ANY($2::uuid[])`,
            [reqId, picked],
          ).then(({ rows: mv }) =>
            expect(Number(mv[0].n), "3 movement rows").to.eq(3),
          );
        });
      });
    });
  });

  it("only PENDING requests cancel — a cancelled one can't be re-cancelled or fulfilled", () => {
    ids(({ kality, awash, cw4 }) => {
      apiPost(superAdmin, "/api/wagon-transfer-requests", {
        fromYardId: kality,
        toYardId: awash,
        wagonTypeId: cw4,
        quantity: 2,
        reason: REASON_B,
      }).then((res) => expect(res.status).to.be.oneOf([200, 201]));

      requestByReason(REASON_B).then(({ rows }) => {
        const reqId = rows[0].id;

        // Withdraw it.
        apiPost(superAdmin, `/api/wagon-transfer-requests/${reqId}/cancel`).then((res) =>
          expect(res.status, "cancelled").to.be.oneOf([200, 201]),
        );
        requestByReason(REASON_B).then(({ rows: r }) =>
          expect(r[0].status, "CANCELLED").to.eq("CANCELLED"),
        );

        // Re-cancel is a conflict.
        apiPost(superAdmin, `/api/wagon-transfer-requests/${reqId}/cancel`, {}, false).then(
          (res) => {
            expect(res.status, "re-cancel conflict").to.eq(409);
            expect(JSON.stringify(res.body)).to.match(/only pending/i);
          },
        );

        // Fulfilling a cancelled request is a conflict too.
        availableCw4("KALITY", 2).then(({ rows: two }) => {
          apiPost(
            superAdmin,
            `/api/wagon-transfer-requests/${reqId}/fulfill`,
            { wagonIds: two.map((w) => w.id) },
            false,
          ).then((res) => {
            expect(res.status, "fulfil-after-cancel conflict").to.eq(409);
            expect(JSON.stringify(res.body)).to.include("already cancelled");
          });
        });
      });
    });
  });
});

export {};
