/**
 * Contract creation → finalization, spanning portal + backoffice:
 *
 *   1. portal (user@gmail.com, company seeded active by seed-company.sql):
 *      wizard → GENERAL / Import / Container (both sizes) → submit + approve quote
 *   2. backoffice marketer: "Accept for approval" (validity) + approve the
 *      LINE_STAFF step
 *   3. backoffice director: approve the DIRECTOR step → PDF → CONTRACT_READY
 *   4. portal customer: scroll contract, agree, draw signature, OTP-sign
 *      → SIGNED_CUSTOMER
 *   5. backoffice marketer: counter-sign as staff → GENERAL contract goes
 *      CONTRACT_ACTIVE (per-booking clearance, no contract-level gate)
 *
 * Sequential steps of one journey — retries off (steps are not idempotent).
 */

const customer = "user@gmail.com";
const companyTin = "0102030405"; // seed-company.sql

/**
 * The journey's contract = the seeded company's latest contract. Each test
 * resolves it from the DB instead of sharing module state — tests stay
 * independently runnable against the current DB state.
 */
function dbContract() {
  return cy.task<{ rows: Array<{ id: string; reference: string; status: string }> }>(
    "db:query",
    {
      sql: `SELECT ct.id, ct.reference, ct.status
            FROM freight.contracts ct
            JOIN freight.companies c ON c.id = ct.company_id
            WHERE c.tin = $1
            ORDER BY ct.created_at DESC LIMIT 1`,
      params: [companyTin],
    },
  );
}

function withContract(fn: (c: { id: string; reference: string; status: string }) => void) {
  dbContract().then(({ rows }) => {
    expect(rows, "latest contract for the seeded company").to.have.length(1);
    fn(rows[0]);
  });
}

function expectStatus(expected: string) {
  dbContract().then(({ rows }) => {
    expect(rows[0]?.status, `contract status`).to.eq(expected);
  });
}

describe("contract lifecycle: creation to finalization", { retries: 0 }, () => {
  before(() => {
    // Re-runnable against a warm DB. The API allows one active contract per
    // customer + service type + route, so this spec's own contract from an
    // earlier run 409s the new one at creation — and since staff accept it
    // with a year's validity, it would keep doing so for a year. Retire just
    // the shape this spec creates (its own wizard-made GENERAL imports),
    // leaving the seeded corridor/segment fixtures alone.
    cy.retireStaleContracts({
      tin: companyTin,
      kind: "GENERAL",
      direction: "IMPORT",
    });
  });

  it("customer creates and submits a GENERAL import container contract", () => {
    cy.loginPortal(customer);
    cy.visitPortal("/contracts/new");

    // Step 0 — Setup.
    cy.mantineSelect(/^Operation Type/, /^Import$/);
    cy.mantineSelect(/^Contract Kind/, "General Contract");
    cy.mantineSelect(/^New or Renewal/, "New Contract");
    cy.contains("Rail Transport Only", { timeout: 15000 }).click();
    cy.mantineSelect(/^Payment Currency/, /^ETB/);
    cy.contains("button", "Continue").click({ force: true });

    // Step 1 — Cargo & Route. Container contracts now auto-cover BOTH 20ft &
    // 40ft (no size picker — just an info card) and the cargo description moved
    // to booking time, so the scope select plus the route is all this step needs.
    cy.mantineSelect(/^Cargo Scope/, /Containerized/);
    cy.mantineSelect(/^Origin Yard/, "Djibouti Port Terminal");
    cy.mantineSelect(/^Destination Yard/, "Mojo Dry Port");
    cy.contains("button", "Continue").click({ force: true });

    // Step 2 — Review & Submit → quotation modal.
    cy.contains("button", "Submit").click({ force: true });
    cy.contains("Approve your quotation", { timeout: 30000 }).should(
      "be.visible",
    );
    cy.contains("button", "Approve & submit").click();

    cy.location("pathname", { timeout: 20000 }).should("eq", "/contracts");
    // `exist`, not `be.visible`: the status badge sits inside the list's
    // horizontally-scrolling container, so Cypress reports it as clipped by an
    // overflow parent. The DB assertion below is the authoritative check.
    cy.contains("Submitted", { timeout: 15000 }).should("exist");

    dbContract().then(({ rows }) => {
      expect(rows, "contract row").to.have.length(1);
      expect(rows[0].status).to.eq("SUBMITTED");
      expect(rows[0].reference).to.match(/^CTR-/);
    });
  });

  it("marketer accepts the submission and approves the LINE_STAFF step", () => {
    cy.loginBackoffice("marketer@edr.local");
    withContract((c) => cy.visit(`/dashboard/contract-requests/${c.id}`));

    cy.contains("button", "Accept for approval", { timeout: 20000 }).click();
    // The modal takes an explicit validity window and pre-fills neither date.
    cy.acceptValidityWindow();
    cy.contains("button", "Accept & start approval", { timeout: 20000 })
      .should("not.be.disabled")
      .click();

    // Approval chain instantiated: LINE_STAFF → DIRECTOR. Approve step 1.
    cy.contains("Approval chain", { timeout: 20000 }).should("be.visible");
    cy.contains("button", "Approve", { timeout: 20000 }).click();
    cy.contains("button", "Confirm approval").click();
    cy.contains("1/2", { timeout: 20000 }).should("be.visible");

    expectStatus("PENDING_APPROVAL");
  });

  it("director approves the final step — contract PDF becomes ready", () => {
    cy.loginBackoffice("director@edr.local");
    withContract((c) => cy.visit(`/dashboard/contract-requests/${c.id}`));

    cy.contains("button", "Approve", { timeout: 20000 }).click();
    cy.contains("button", "Confirm approval").click();

    // Final approval renders the contract PDF synchronously → CONTRACT_READY.
    // The approval-chain card unmounts once the contract leaves approval, so
    // assert on the signing CTA that replaces it.
    cy.contains("button", "View & sign", { timeout: 30000 }).should("exist");

    expectStatus("CONTRACT_READY");
  });

  it("customer signs the contract with OTP", () => {
    cy.loginPortal(customer);
    withContract((c) => cy.visitPortal(`/contracts/${c.id}/view`));

    // Scroll the contract iframe to the bottom so the consent bar unlocks.
    // Retried because the iframe can re-render (query refetch) after a scroll.
    const unlockConsent = (attempt: number) => {
      cy.get('iframe[title="Contract document"]', { timeout: 30000 }).then(
        ($f) => {
          const win = ($f[0] as HTMLIFrameElement).contentWindow;
          // documentElement can be null while the srcDoc is (re)parsing —
          // skip this round and let the retry pick it up.
          const el =
            win?.document?.scrollingElement ?? win?.document?.documentElement;
          if (win && el) {
            el.scrollTop = el.scrollHeight;
            win.dispatchEvent(new Event("scroll"));
          }
        },
      );
      cy.wait(500).then(() => {
        cy.get("body").then(($b) => {
          // Probe the description that only renders once hasScrolledToBottom
          // flips — the consent LABEL is always in the DOM (just its checkbox
          // is disabled), so matching on it raced ahead of the scroll handler
          // and left the Sign button disabled.
          if ($b.text().includes("You may now sign the contract")) return;
          expect(attempt, "consent bar unlocked").to.be.lessThan(20);
          unlockConsent(attempt + 1);
        });
      });
    };
    unlockConsent(0);

    // Check the input itself — clicking the label text lands on the Checkbox's
    // label span, which does not toggle it, leaving Sign disabled.
    cy.contains("I have read the entire contract", { timeout: 15000 })
      .closest(".mantine-Checkbox-root")
      .find('input[type="checkbox"]')
      .check({ force: true });
    cy.contains("button", /^Sign contract$|^Approve & sign$/).click();

    // Signature modal: name + drawn signature.
    cy.contains("label", "Full name")
      .invoke("attr", "for")
      .then((id) => {
        cy.get(`[id="${id}"]`).clear().type("Demo User");
      });
    cy.drawSignature();
    cy.uploadCompanyStamp();
    cy.contains("button", "Continue to verification").click();

    // OTP modal — code goes to the signer's registered contacts (email only
    // for the seeded demo user); read it from the DB.
    cy.contains("Verify it's you", { timeout: 20000 }).should("be.visible");
    cy.getOtp(customer).then((otp) => cy.typeOtp(otp));
    cy.contains("button", "Verify & sign").click();

    cy.contains("Your signature has been recorded", { timeout: 30000 }).should(
      "be.visible",
    );
    expectStatus("SIGNED_CUSTOMER");
  });

  it("staff counter-signs — GENERAL contract becomes CONTRACT_ACTIVE", () => {
    cy.loginBackoffice("marketer@edr.local");
    withContract((c) => cy.visit(`/dashboard/contract-requests/${c.id}/view`));

    cy.contains("button", /^Sign as staff$|^Approve & sign$/, {
      timeout: 30000,
    }).click();
    cy.contains("label", "Full name")
      .invoke("attr", "for")
      .then((id) => {
        cy.get(`[id="${id}"]`).clear().type("EDR Marketer");
      });
    cy.drawSignature();
    // Staff counter-sign gates on a stamp too, same as the customer's modal.
    cy.uploadCompanyStamp();
    // Scoped to the modal — the toolbar behind it has its own "Approve & sign".
    cy.get(".mantine-Modal-content")
      .contains("button", /^Confirm signature$|^Approve & sign$/)
      .click();

    cy.contains("counter-signed", { timeout: 30000 }).should("be.visible");

    // GENERAL → clearance runs per booking, contract goes straight active.
    expectStatus("CONTRACT_ACTIVE");

    // Both signatures recorded.
    withContract((c) => {
      cy.task<{ rows: Array<{ role: string }> }>("db:query", {
        sql: `SELECT s.role FROM freight.contract_signatures s
              WHERE s.contract_id = $1 ORDER BY s.role`,
        params: [c.id],
      }).then(({ rows }) => {
        expect(rows.map((r) => r.role)).to.include.members(["CUSTOMER", "STAFF"]);
      });
    });
  });
});

export {};
