const { expect } = require("chai");
const { ethers, upgrades } = require("hardhat");

describe("ShipmentTracker", function () {
  let shipmentTracker;
  let owner, manufacturer, buyer, logistics, other;

  beforeEach(async function () {
    [owner, manufacturer, buyer, logistics, other] = await ethers.getSigners();

    const ShipmentTracker = await ethers.getContractFactory("ShipmentTracker");
    shipmentTracker = await upgrades.deployProxy(
      ShipmentTracker,
      [owner.address],
      { initializer: "initialize" }
    );
    await shipmentTracker.waitForDeployment();

    // Grant roles
    const MANUFACTURER_ROLE = await shipmentTracker.MANUFACTURER_ROLE();
    const BUYER_ROLE = await shipmentTracker.BUYER_ROLE();
    const LOGISTICS_ROLE = await shipmentTracker.LOGISTICS_ROLE();

    await shipmentTracker.grantRole(MANUFACTURER_ROLE, manufacturer.address);
    await shipmentTracker.grantRole(BUYER_ROLE, buyer.address);
    await shipmentTracker.grantRole(LOGISTICS_ROLE, logistics.address);
  });

  describe("Deployment", function () {
    it("Should set the correct admin", async function () {
      const ADMIN_ROLE = await shipmentTracker.ADMIN_ROLE();
      expect(await shipmentTracker.hasRole(ADMIN_ROLE, owner.address)).to.be.true;
    });

    it("Should grant manufacturer role", async function () {
      const MANUFACTURER_ROLE = await shipmentTracker.MANUFACTURER_ROLE();
      expect(await shipmentTracker.hasRole(MANUFACTURER_ROLE, manufacturer.address)).to.be.true;
    });
  });

  describe("Create Shipment", function () {
    it("Should create a shipment successfully", async function () {
      const dataHash = ethers.keccak256(ethers.toUtf8Bytes("test data"));

      const tx = await shipmentTracker.connect(manufacturer).createShipment(
        "SHIP-001",
        manufacturer.address,
        "Manufacturer A",
        "MFG-001",
        buyer.address,
        "Buyer B",
        "BYR-001",
        dataHash
      );

      await expect(tx)
        .to.emit(shipmentTracker, "ShipmentCreated")
        .withArgs(1, "SHIP-001", manufacturer.address, buyer.address, await ethers.provider.getBlock("latest").then(b => b.timestamp));

      const shipmentId = await shipmentTracker.getShipmentByExternalId("SHIP-001");
      expect(shipmentId).to.equal(1);
    });

    it("Should fail to create duplicate shipment", async function () {
      const dataHash = ethers.keccak256(ethers.toUtf8Bytes("test data"));

      await shipmentTracker.connect(manufacturer).createShipment(
        "SHIP-001",
        manufacturer.address,
        "Manufacturer A",
        "MFG-001",
        buyer.address,
        "Buyer B",
        "BYR-001",
        dataHash
      );

      await expect(
        shipmentTracker.connect(manufacturer).createShipment(
          "SHIP-001",
          manufacturer.address,
          "Manufacturer A",
          "MFG-001",
          buyer.address,
          "Buyer B",
          "BYR-001",
          dataHash
        )
      ).to.be.revertedWith("Shipment with this external ID already exists");
    });

    it("Should fail without proper role", async function () {
      const dataHash = ethers.keccak256(ethers.toUtf8Bytes("test data"));

      await expect(
        shipmentTracker.connect(other).createShipment(
          "SHIP-001",
          manufacturer.address,
          "Manufacturer A",
          "MFG-001",
          buyer.address,
          "Buyer B",
          "BYR-001",
          dataHash
        )
      ).to.be.revertedWith("Not authorized to create shipment");
    });
  });

  describe("Update Logistics Info", function () {
    beforeEach(async function () {
      const dataHash = ethers.keccak256(ethers.toUtf8Bytes("test data"));
      await shipmentTracker.connect(manufacturer).createShipment(
        "SHIP-001",
        manufacturer.address,
        "Manufacturer A",
        "MFG-001",
        buyer.address,
        "Buyer B",
        "BYR-001",
        dataHash
      );
    });

    it("Should update logistics info", async function () {
      const logisticsHash = ethers.keccak256(ethers.toUtf8Bytes("logistics data"));

      await expect(
        shipmentTracker.connect(logistics).updateLogisticsInfo(
          1,
          logistics.address,
          "Logistics C",
          "LOG-001",
          logisticsHash
        )
      )
        .to.emit(shipmentTracker, "LogisticsInfoUpdated")
        .withArgs(1, logistics.address, logisticsHash, await ethers.provider.getBlock("latest").then(b => b.timestamp));

      const shipment = await shipmentTracker.getShipment(1);
      expect(shipment.logistics.walletAddress).to.equal(logistics.address);
      expect(shipment.logistics.name).to.equal("Logistics C");
    });
  });

  describe("Update Shipment Status", function () {
    beforeEach(async function () {
      const dataHash = ethers.keccak256(ethers.toUtf8Bytes("test data"));
      await shipmentTracker.connect(manufacturer).createShipment(
        "SHIP-001",
        manufacturer.address,
        "Manufacturer A",
        "MFG-001",
        buyer.address,
        "Buyer B",
        "BYR-001",
        dataHash
      );

      const logisticsHash = ethers.keccak256(ethers.toUtf8Bytes("logistics data"));
      await shipmentTracker.connect(logistics).updateLogisticsInfo(
        1,
        logistics.address,
        "Logistics C",
        "LOG-001",
        logisticsHash
      );
    });

    it("Should update status to ManufacturerConfirmed", async function () {
      await expect(
        shipmentTracker.connect(manufacturer).updateShipmentStatus(1, 1) // ManufacturerConfirmed
      )
        .to.emit(shipmentTracker, "ShipmentUpdated")
        .withArgs(1, 1, manufacturer.address, await ethers.provider.getBlock("latest").then(b => b.timestamp));

      const shipment = await shipmentTracker.getShipment(1);
      expect(shipment.status).to.equal(1);
    });

    it("Should update status to InTransit by logistics", async function () {
      await shipmentTracker.connect(manufacturer).updateShipmentStatus(1, 1);

      await expect(
        shipmentTracker.connect(logistics).updateShipmentStatus(1, 2) // InTransit
      )
        .to.emit(shipmentTracker, "ShipmentUpdated");

      const shipment = await shipmentTracker.getShipment(1);
      expect(shipment.status).to.equal(2);
    });

    it("Should fail to update status without authorization", async function () {
      await expect(
        shipmentTracker.connect(other).updateShipmentStatus(1, 1)
      ).to.be.revertedWith("Not authorized");
    });
  });

  describe("Add Document", function () {
    beforeEach(async function () {
      const dataHash = ethers.keccak256(ethers.toUtf8Bytes("test data"));
      await shipmentTracker.connect(manufacturer).createShipment(
        "SHIP-001",
        manufacturer.address,
        "Manufacturer A",
        "MFG-001",
        buyer.address,
        "Buyer B",
        "BYR-001",
        dataHash
      );
    });

    it("Should add a document", async function () {
      const docHash = "QmTest123456789";

      await expect(
        shipmentTracker.connect(manufacturer).addDocument(1, docHash)
      )
        .to.emit(shipmentTracker, "DocumentAdded")
        .withArgs(1, docHash, manufacturer.address, await ethers.provider.getBlock("latest").then(b => b.timestamp));

      const documents = await shipmentTracker.getShipmentDocuments(1);
      expect(documents).to.include(docHash);
    });
  });

  describe("Pause/Unpause", function () {
    it("Should pause and unpause contract", async function () {
      await shipmentTracker.pause();

      const dataHash = ethers.keccak256(ethers.toUtf8Bytes("test data"));
      await expect(
        shipmentTracker.connect(manufacturer).createShipment(
          "SHIP-001",
          manufacturer.address,
          "Manufacturer A",
          "MFG-001",
          buyer.address,
          "Buyer B",
          "BYR-001",
          dataHash
        )
      ).to.be.revertedWith("Pausable: paused");

      await shipmentTracker.unpause();

      await expect(
        shipmentTracker.connect(manufacturer).createShipment(
          "SHIP-001",
          manufacturer.address,
          "Manufacturer A",
          "MFG-001",
          buyer.address,
          "Buyer B",
          "BYR-001",
          dataHash
        )
      ).to.emit(shipmentTracker, "ShipmentCreated");
    });
  });
});
