/**
 * RULES & CONFIG — rate lifecycle + live rate-change mid-window:
 *
 *   A LIVE rate is what pricing charges right now, so it is never edited in
 *   place (rates.service.ts `applyApprovedUpdate` doc comment) — an edit is
 *   filed as a change request and only takes effect once approved. This spec
 *   drives the full lifecycle end to end, something the unit tests never do:
 *
 *     – create (DRAFT) → duplicate-combo rejected → submit → PENDING_APPROVAL
 *       → a non-DRAFT edit is rejected → approve → LIVE, visible on /rates/live
 *     – re-approving an already-LIVE rate is rejected
 *     – a rate-change-request proposing the SAME value is rejected ("nothing
 *       changed"); a real change files PENDING; a second concurrent change is
 *       rejected ("already has a change awaiting approval")
 *     – approving the change updates the LIVE row'S VALUE IN PLACE (same rate
 *       id, same row) — the "mid-window" live edit this whole flow protects
 *     – re-approving the same (now-decided) change request is rejected
 *
 * BULK/ALWAYS/IMPORT rate on a yard pair the corridor seed never touches
 * (DJIB_PORT → E2E_AWASH), so the duplicate-combo guard has a clean slate.
 * Retries off — sequential steps of one rate's lifecycle.
 */

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

interface RateRow {
  id: string;
  status: string;
  rate_value: string;
}

function rateByPattern() {
  return db<RateRow>(
    `SELECT r.id, r.status, r.rate_value
       FROM freight.rates r
       JOIN freight.yards o ON o.id = r.origin_yard_id AND o.code = 'DJIB_PORT'
       JOIN freight.yards d ON d.id = r.destination_yard_id AND d.code = 'E2E_AWASH'
      WHERE r.rate_type = 'BULK_IMPORT' AND r.rate_unit = 'PER_TON'
        AND r.deleted_at IS NULL
      ORDER BY r.created_at DESC LIMIT 1`,
  ).then(({ rows }) => {
    expect(rows, "the test rate").to.have.length(1);
    return rows[0];
  });
}

interface ChangeRequestRow {
  id: string;
  status: string;
  rate_id: string;
}

function changeRequestByRate(rateId: string) {
  return db<ChangeRequestRow>(
    `SELECT id, status, rate_id FROM freight.rate_change_requests
      WHERE rate_id = $1 ORDER BY created_at DESC LIMIT 1`,
    [rateId],
  ).then(({ rows }) => {
    expect(rows, "change request for this rate").to.have.length(1);
    return rows[0];
  });
}

function yardId(code: string) {
  return db<{ id: string }>(`SELECT id FROM freight.yards WHERE code = $1`, [code]).then(
    ({ rows }) => rows[0].id,
  );
}

const RATE_BODY = (originYardId: string, destinationYardId: string, rateValue: number) => ({
  appliesTo: "BULK",
  trigger: "ALWAYS",
  tradeDirection: "IMPORT",
  originYardId,
  destinationYardId,
  rateValue,
  rateUnit: "PER_TON",
});

describe("rules & config: rate lifecycle + live rate-change mid-window", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    // Soft-delete any leftover rate from a prior run of this spec — the
    // duplicate-combo guard under test would otherwise reject THIS run's
    // very first create against yesterday's row on a persistent DB.
    db(
      `UPDATE freight.rates r
         SET deleted_at = now()
        FROM freight.yards o, freight.yards d
       WHERE r.origin_yard_id = o.id AND o.code = 'DJIB_PORT'
         AND r.destination_yard_id = d.id AND d.code = 'E2E_AWASH'
         AND r.rate_type = 'BULK_IMPORT' AND r.rate_unit = 'PER_TON'
         AND r.deleted_at IS NULL`,
    );
  });

  it("creates a DRAFT rate — a duplicate combination is rejected", () => {
    yardId("DJIB_PORT").then((originYardId) => {
      yardId("E2E_AWASH").then((destinationYardId) => {
        apiPost(superAdmin, "/api/rates", RATE_BODY(originYardId, destinationYardId, 100)).then(
          (res) => expect(res.status, "rate created").to.be.oneOf([200, 201]),
        );

        apiPost(
          superAdmin,
          "/api/rates",
          RATE_BODY(originYardId, destinationYardId, 999),
          false,
        ).then((res) => {
          expect(res.status, "duplicate combo rejected").to.eq(409);
          expect(JSON.stringify(res.body)).to.include("already exists on this route");
        });
      });
    });

    rateByPattern().then((r) => {
      expect(r.status, "starts DRAFT").to.eq("DRAFT");
      expect(Number(r.rate_value), "original value 100").to.eq(100);
    });
  });

  it("submit → PENDING_APPROVAL; a non-DRAFT rate can't be edited directly", () => {
    rateByPattern().then((r) => {
      apiPost(superAdmin, `/api/rates/${r.id}/submit`).then((res) =>
        expect(res.status, "submitted").to.be.oneOf([200, 201]),
      );
    });

    rateByPattern().then((r) => {
      expect(r.status, "PENDING_APPROVAL").to.eq("PENDING_APPROVAL");

      apiPost(superAdmin, `/api/rates/${r.id}`, { rateValue: 150 }, false).then((res) => {
        expect(res.status, "direct edit rejected").to.be.within(400, 422);
      });
    });
  });

  it("approve → LIVE, visible on /rates/live; re-approving is rejected", () => {
    rateByPattern().then((r) => {
      apiPost(superAdmin, `/api/rates/${r.id}/approve`).then((res) =>
        expect(res.status, "approved").to.be.oneOf([200, 201]),
      );
    });

    rateByPattern().then((r) => {
      expect(r.status, "LIVE").to.eq("LIVE");
      expect(Number(r.rate_value), "still 100 at approval").to.eq(100);

      db<{ n: string }>(
        `SELECT count(*) AS n FROM freight.rates WHERE id = $1 AND status = 'LIVE'`,
        [r.id],
      ).then(({ rows }) => expect(Number(rows[0].n), "present as LIVE").to.eq(1));

      apiPost(superAdmin, `/api/rates/${r.id}/approve`, {}, false).then((res) => {
        expect(res.status, "re-approve rejected").to.be.within(400, 422);
        expect(JSON.stringify(res.body)).to.include("PENDING_APPROVAL");
      });
    });
  });

  it("a change proposing the SAME value is rejected — nothing changed", () => {
    rateByPattern().then((r) => {
      apiPost(
        superAdmin,
        "/api/rate-change-requests",
        { rateId: r.id, update: { rateValue: 100 } },
        false,
      ).then((res) => {
        expect(res.status, "no-op change rejected").to.eq(400);
        expect(JSON.stringify(res.body)).to.include("Nothing changed");
      });
    });
  });

  it("a real change files PENDING; a second concurrent change is rejected", () => {
    rateByPattern().then((r) => {
      apiPost(superAdmin, "/api/rate-change-requests", {
        rateId: r.id,
        update: { rateValue: 250 },
      }).then((res) => expect(res.status, "change filed").to.be.oneOf([200, 201]));

      apiPost(
        superAdmin,
        "/api/rate-change-requests",
        { rateId: r.id, update: { rateValue: 300 } },
        false,
      ).then((res) => {
        expect(res.status, "second concurrent change rejected").to.eq(409);
        expect(JSON.stringify(res.body)).to.include("already has a change awaiting approval");
      });

      changeRequestByRate(r.id).then((cr) => {
        expect(cr.status, "PENDING").to.eq("PENDING");
      });

      // The rate itself is untouched while the change is pending.
      rateByPattern().then((live) =>
        expect(Number(live.rate_value), "still 100 while pending").to.eq(100),
      );
    });
  });

  it("approving the change updates the LIVE row in place; re-approving it is rejected", () => {
    rateByPattern().then((r) => {
      changeRequestByRate(r.id).then((cr) => {
        apiPost(superAdmin, `/api/rate-change-requests/${cr.id}/approve`, {}).then((res) =>
          expect(res.status, "change approved").to.be.oneOf([200, 201]),
        );
      });
    });

    rateByPattern().then((r) => {
      expect(r.status, "still LIVE (same row)").to.eq("LIVE");
      expect(Number(r.rate_value), "value now 250").to.eq(250);

      changeRequestByRate(r.id).then((cr) => {
        expect(cr.status, "APPROVED").to.eq("APPROVED");
        apiPost(superAdmin, `/api/rate-change-requests/${cr.id}/approve`, {}, false).then(
          (res) => {
            expect(res.status, "re-approve rejected").to.eq(409);
            expect(JSON.stringify(res.body)).to.include("already approved");
          },
        );
      });
    });
  });
});

export {};
