import { describe, expect, inject, it } from "vitest";
import { createGraphQLClient, graphql } from "@/tests/utils/graphql-client";

interface AttributeValue {
  id: string;
  label: string;
}

const listUnits = graphql(`
  query ListUnitsForAssign {
    units {
      edges {
        node {
          id
        }
      }
    }
  }
`);

const createAttribute = graphql(`
  mutation CreateAttributeForAssign($code: String!, $name: String!, $valueLabels: [String!]) {
    createAttribute(code: $code, name: $name, valueLabels: $valueLabels) {
      id
    }
  }
`);

const getAttributeValues = graphql(`
  query GetAttributeValuesForAssign($id: ID!) {
    productAttribute(id: $id) {
      id
      productAttributeValues {
        edges {
          node {
            id
            label
          }
        }
      }
    }
  }
`);

const createProduct = graphql(`
  mutation CreateProductForAssign(
    $code: String!
    $name: String!
    $unitId: ID!
    $attributes: [CreateProductInputAttributes!]
  ) {
    createProduct(code: $code, name: $name, unitId: $unitId, attributes: $attributes) {
      id
      status
    }
  }
`);

const updateProductAttributes = graphql(`
  mutation UpdateProductAttributesForAssign(
    $productId: ID!
    $attributes: [UpdateProductAttributesInputAttributes!]!
  ) {
    updateProductAttributes(productId: $productId, attributes: $attributes) {
      productId
    }
  }
`);

const activateProduct = graphql(`
  mutation ActivateProductForAssign($id: ID!) {
    activateProduct(id: $id) {
      id
      status
    }
  }
`);

const deactivateProduct = graphql(`
  mutation DeactivateProductForAssign($id: ID!) {
    deactivateProduct(id: $id) {
      id
      status
    }
  }
`);

describe("product-manager--assign-attribute-to-product", () => {
  const gql = createGraphQLClient(inject("url"), inject("token"));

  async function createAttributeWithValues(codeSuffix: string) {
    const ts = Date.now();
    const res = await gql
      .mutation(createAttribute, {
        code: `ASSIGN-${codeSuffix}-${ts}`,
        name: `Attr ${codeSuffix}`,
        valueLabels: ["Val1", "Val2"],
      })
      .toPromise();
    const attrId = res.data!.createAttribute.id;
    const valuesRes = await gql.query(getAttributeValues, { id: attrId }).toPromise();
    const values = valuesRes.data!.productAttribute!.productAttributeValues.edges.map(
      (e) => e.node,
    ) as AttributeValue[];
    return {
      attributeId: attrId,
      values,
    };
  }

  it("Given a DRAFT product, when an attribute is assigned, then the attribute is linked to the product.", async () => {
    const ts = Date.now();
    const unitsResult = await gql.query(listUnits, {}).toPromise();
    const unitId = unitsResult.data!.units.edges[0].node.id;

    const attr = await createAttributeWithValues("DRAFT");

    const productRes = await gql
      .mutation(createProduct, {
        code: `DRAFT-ASSIGN-${ts}`,
        name: "Draft Assign Product",
        unitId,
      })
      .toPromise();
    const productId = productRes.data!.createProduct.id;
    expect(productRes.data?.createProduct.status).toBe("DRAFT");

    const res = await gql
      .mutation(updateProductAttributes, {
        productId,
        attributes: [{ attributeId: attr.attributeId, valueId: attr.values[0].id }] as never,
      })
      .toPromise();

    expect(res.data?.updateProductAttributes.productId).toBe(productId);
  });

  it("Given an ACTIVE product, when a new attribute is assigned, then it fails because axis structure is locked.", async () => {
    const ts = Date.now();
    const unitsResult = await gql.query(listUnits, {}).toPromise();
    const unitId = unitsResult.data!.units.edges[0].node.id;

    const attr1 = await createAttributeWithValues("ACT1");
    const attr2 = await createAttributeWithValues("ACT2");

    const productRes = await gql
      .mutation(createProduct, {
        code: `ACTIVE-ASSIGN-${ts}`,
        name: "Active Assign Product",
        unitId,
        attributes: [{ attributeId: attr1.attributeId, valueId: attr1.values[0].id }] as never,
      })
      .toPromise();
    const productId = productRes.data!.createProduct.id;

    await gql.mutation(activateProduct, { id: productId }).toPromise();

    await expect(
      gql
        .mutation(updateProductAttributes, {
          productId,
          attributes: [
            { attributeId: attr1.attributeId, valueId: attr1.values[0].id },
            { attributeId: attr2.attributeId, valueId: attr2.values[0].id },
          ] as never,
        })
        .toPromise(),
    ).rejects.toThrow();
  });

  it("Given an ACTIVE product, when an attempt is made to remove an attribute, then an error is shown.", async () => {
    const ts = Date.now();
    const unitsResult = await gql.query(listUnits, {}).toPromise();
    const unitId = unitsResult.data!.units.edges[0].node.id;

    const attr1 = await createAttributeWithValues("REM1");
    const attr2 = await createAttributeWithValues("REM2");

    const productRes = await gql
      .mutation(createProduct, {
        code: `ACTIVE-REM-${ts}`,
        name: "Active Remove Product",
        unitId,
        attributes: [
          { attributeId: attr1.attributeId, valueId: attr1.values[0].id },
          { attributeId: attr2.attributeId, valueId: attr2.values[0].id },
        ] as never,
      })
      .toPromise();
    const productId = productRes.data!.createProduct.id;

    await gql.mutation(activateProduct, { id: productId }).toPromise();

    // Attempt to remove attr2 by only sending attr1.
    await expect(
      gql
        .mutation(updateProductAttributes, {
          productId,
          attributes: [{ attributeId: attr1.attributeId, valueId: attr1.values[0].id }] as never,
        })
        .toPromise(),
    ).rejects.toThrow();
  });

  it("Given an ARCHIVED product, when an attribute assignment is attempted, then an error is shown.", async () => {
    const ts = Date.now();
    const unitsResult = await gql.query(listUnits, {}).toPromise();
    const unitId = unitsResult.data!.units.edges[0].node.id;

    const attr = await createAttributeWithValues("ARC");

    const productRes = await gql
      .mutation(createProduct, {
        code: `ARCHIVED-ASSIGN-${ts}`,
        name: "Archived Assign Product",
        unitId,
      })
      .toPromise();
    const productId = productRes.data!.createProduct.id;

    await gql.mutation(activateProduct, { id: productId }).toPromise();
    await gql.mutation(deactivateProduct, { id: productId }).toPromise();

    await expect(
      gql
        .mutation(updateProductAttributes, {
          productId,
          attributes: [{ attributeId: attr.attributeId, valueId: attr.values[0].id }] as never,
        })
        .toPromise(),
    ).rejects.toThrow();
  });
});
