/**
 * CONTRACT — rejection / send-back (import & export contracts):
 *
 *   Covered elsewhere only for the INTERCITY contract wizard (rejection +
 *   reason + resubmit). This spec drives the CONTRACT-level reject/
 *   request-changes/approval-chain guards directly via the API against a
 *   plain IMPORT container contract, something no other spec exercises:
 *
 *     – SUBMITTED → staff reject → REJECTED (terminal, reason recorded)
 *     – SUBMITTED → staff request-changes → CHANGES_REQUESTED
 *     – SUBMITTED → staff accept → PENDING_APPROVAL (chain instantiated) →
 *       reject the first step straight to the customer (no returnToStepId)
 *       → REJECTED
 *     – SUBMITTED → accept → approve step 1 → reject step 2 WITH
 *       returnToStepId=step1 (send-back) → step 1 and 2 both reset to
 *       PENDING, contract stays PENDING_APPROVAL (alive, not rejected)
 *     – guard: a rejection can only return to an EARLIER step
 *
 * Contracts are seeded directly at SUBMITTED (skipping the wizard — that
 * journey is covered by contract-lifecycle.cy.ts) so this spec is pure API.
 * Retries off — each `it` is an independent contract's own short journey.
 */

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

const stamp = String(Date.now());
let seq = 0;

/** A plain IMPORT/CONTAINER/ONE_TIME contract seeded straight to SUBMITTED —
 * no cargo scope (so instantiateApprovalSteps resolves the non-director
 * chain), no pricing (reject/request-changes/accept never read it). */
function seedSubmittedContract(): Cypress.Chainable<string> {
  const reference = `CTR-REJ-${stamp}-${seq++}`;
  return db<{ id: string }>(
    `WITH c AS (
       INSERT INTO freight.contracts
         (reference, company_id, company_profile_id, contract_kind,
          trade_direction, freight_type, service_type_id, payment_currency,
          customs_clearing_enabled, clearance_status, status, contract_summary)
       SELECT $1, comp.id,
              (SELECT p.id FROM freight.company_profiles p
                WHERE p.company_id = comp.id AND p.deleted_at IS NULL
                ORDER BY CASE WHEN p.type = 'importer' THEN 0 ELSE 1 END
                LIMIT 1),
              'ONE_TIME', 'IMPORT', 'CONTAINER',
              (SELECT st.id FROM freight.service_types st ORDER BY st.created_at LIMIT 1),
              'ETB', false, 'NOT_APPLICABLE', 'SUBMITTED', 'E2E contract-reject fixture'
       FROM freight.companies comp WHERE comp.tin = $2
       RETURNING id
     )
     INSERT INTO freight.contract_routes
       (contract_id, origin_yard_id, destination_yard_id, sort_order)
     SELECT c.id, o.id, d.id, 0 FROM c
     JOIN freight.yards o ON o.code = $3
     JOIN freight.yards d ON d.code = $4
     RETURNING contract_id AS id`,
    [reference, companyTin, ORIGIN, DEST],
  ).then(({ rows }) => {
    expect(rows, "seeded SUBMITTED contract").to.have.length(1);
    return cy.wrap(rows[0].id, { log: false });
  });
}

interface ContractRow {
  status: string;
}

function contractStatus(id: string, fn: (row: ContractRow) => void) {
  db<ContractRow>(`SELECT status FROM freight.contracts WHERE id = $1`, [id]).then(
    ({ rows }) => {
      expect(rows, "contract row").to.have.length(1);
      fn(rows[0]);
    },
  );
}

interface StepRow {
  id: string;
  step_order: number;
  status: string;
}

function approvalSteps(contractId: string, fn: (rows: StepRow[]) => void) {
  db<StepRow>(
    `SELECT id, step_order, status FROM freight.contract_approval_steps
      WHERE contract_id = $1 AND deleted_at IS NULL ORDER BY step_order ASC`,
    [contractId],
  ).then(({ rows }) => fn(rows));
}

describe("contract: rejection and approval-chain send-back", { retries: 0 }, () => {
  it("SUBMITTED → staff reject → REJECTED, reason recorded", () => {
    seedSubmittedContract().then((id) => {
      apiPost(superAdmin, `/api/contracts/${id}/staff/reject`, {
        reason: "E2E: missing documents",
      }).then((res) => expect(res.status, "rejected").to.be.oneOf([200, 201]));

      contractStatus(id, (c) => expect(c.status, "REJECTED").to.eq("REJECTED"));

      db<{ n: string }>(
        `SELECT count(*) AS n FROM freight.contract_review_notes
          WHERE contract_id = $1 AND note_type = 'REJECTION'`,
        [id],
      ).then(({ rows }) => expect(Number(rows[0].n), "rejection note recorded").to.be.at.least(1));

      // Terminal: rejecting again is refused by the status guard.
      apiPost(
        superAdmin,
        `/api/contracts/${id}/staff/reject`,
        { reason: "again" },
        false,
      ).then((res) => expect(res.status, "re-reject refused").to.be.within(400, 422));
    });
  });

  it("SUBMITTED → staff request-changes → CHANGES_REQUESTED", () => {
    seedSubmittedContract().then((id) => {
      apiPost(superAdmin, `/api/contracts/${id}/staff/request-changes`, {
        note: "E2E: please add the missing cargo description",
      }).then((res) => expect(res.status, "changes requested").to.be.oneOf([200, 201]));

      contractStatus(id, (c) =>
        expect(c.status, "CHANGES_REQUESTED").to.eq("CHANGES_REQUESTED"),
      );

      // request-changes without a note is refused.
      seedSubmittedContract().then((id2) => {
        apiPost(
          superAdmin,
          `/api/contracts/${id2}/staff/request-changes`,
          {},
          false,
        ).then((res) => expect(res.status, "note required").to.be.within(400, 422));
      });
    });
  });

  it("accept → PENDING_APPROVAL; rejecting the first step straight to the customer → REJECTED", () => {
    seedSubmittedContract().then((id) => {
      apiPost(superAdmin, `/api/contracts/${id}/staff/accept`, { validityDays: 365 }).then(
        (res) => expect(res.status, "accepted into chain").to.be.oneOf([200, 201]),
      );

      approvalSteps(id, (steps) => {
        expect(steps.length, "chain has at least one step").to.be.at.least(1);
        const step1 = steps[0];

        apiPost(superAdmin, `/api/contracts/${id}/approval-steps/${step1.id}/reject`, {
          reason: "E2E: rejected to customer",
        }).then((res) => expect(res.status, "rejected to customer").to.be.oneOf([200, 201]));
      });

      contractStatus(id, (c) => expect(c.status, "REJECTED (terminal)").to.eq("REJECTED"));
    });
  });

  it("send-back: reject step 2 back to step 1 — both reset PENDING, contract stays alive", () => {
    seedSubmittedContract().then((id) => {
      apiPost(superAdmin, `/api/contracts/${id}/staff/accept`, { validityDays: 365 });

      approvalSteps(id, (steps) => {
        expect(steps.length, "chain has at least 2 steps for a send-back").to.be.at.least(2);
        const [step1, step2] = steps;

        apiPost(superAdmin, `/api/contracts/${id}/approval-steps/${step1.id}/approve`).then(
          (res) => expect(res.status, "step 1 approved").to.be.oneOf([200, 201]),
        );

        // Guard: a rejection can only return to an EARLIER step — step2 → step2 rejected.
        apiPost(
          superAdmin,
          `/api/contracts/${id}/approval-steps/${step2.id}/reject`,
          { reason: "bad", returnToStepId: step2.id },
          false,
        ).then((res) => {
          expect(res.status, "same-step return rejected").to.be.within(400, 422);
          expect(JSON.stringify(res.body)).to.include("EARLIER step");
        });

        apiPost(superAdmin, `/api/contracts/${id}/approval-steps/${step2.id}/reject`, {
          reason: "E2E: send back for a fix",
          returnToStepId: step1.id,
        }).then((res) => expect(res.status, "sent back to step 1").to.be.oneOf([200, 201]));
      });

      contractStatus(id, (c) =>
        expect(c.status, "still PENDING_APPROVAL (alive)").to.eq("PENDING_APPROVAL"),
      );
      approvalSteps(id, (steps) => {
        expect(steps[0].status, "step 1 reset to PENDING").to.eq("PENDING");
        expect(steps[1].status, "step 2 reset to PENDING").to.eq("PENDING");
      });
    });
  });
});

export {};
