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

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

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

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

const updateAttribute = graphql(`
  mutation UpdateAttribute($id: ID!, $name: String, $values: [UpdateAttributeInputValues]) {
    updateAttribute(id: $id, name: $name, values: $values) {
      id
    }
  }
`);

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

  it("Given a valid value name, when adding a value, then the value is created and listed under the attribute.", async () => {
    const code = `MV-ADD-${Date.now()}`;
    const createRes = await gql
      .mutation(createAttribute, {
        code,
        name: "Add Value Attr",
        valueLabels: ["Existing"],
      })
      .toPromise();
    const attributeId = createRes.data!.createAttribute.id;

    const detailBefore = await gql.query(getAttributeDetail, { id: attributeId }).toPromise();
    const existingValues = detailBefore.data!.productAttribute!.productAttributeValues.edges.map(
      (e) => e.node,
    ) as AttributeValue[];

    // Add a new value by including existing values plus a new one without id.
    await gql
      .mutation(updateAttribute, {
        id: attributeId,
        values: [
          ...existingValues.map((v: AttributeValue) => ({ id: v.id, label: v.label })),
          { label: "NewValue" },
        ] as never,
      })
      .toPromise();

    const detailAfter = await gql.query(getAttributeDetail, { id: attributeId }).toPromise();
    const afterValues = detailAfter.data!.productAttribute!.productAttributeValues.edges.map(
      (e) => e.node,
    ) as AttributeValue[];
    const labels = afterValues.map((v: AttributeValue) => v.label);
    expect(labels).toContain("Existing");
    expect(labels).toContain("NewValue");
    expect(afterValues).toHaveLength(2);
  });

  it("Given a missing required field, when adding or editing a value, then a validation error is shown.", async () => {
    const code = `MV-MISS-${Date.now()}`;
    const createRes = await gql
      .mutation(createAttribute, {
        code,
        name: "Missing Field Attr",
        valueLabels: ["Val1"],
      })
      .toPromise();
    const attributeId = createRes.data!.createAttribute.id;

    await expect(
      gql
        .mutation(updateAttribute, {
          id: attributeId,
          values: [{ label: "" }] as never,
        })
        .toPromise(),
    ).rejects.toThrow();
  });

  it("Given an existing value, when edited with a valid name, then the value is updated.", async () => {
    const code = `MV-EDIT-${Date.now()}`;
    const createRes = await gql
      .mutation(createAttribute, {
        code,
        name: "Edit Value Attr",
        valueLabels: ["Original"],
      })
      .toPromise();
    const attributeId = createRes.data!.createAttribute.id;

    const detailBefore = await gql.query(getAttributeDetail, { id: attributeId }).toPromise();
    const beforeValues = detailBefore.data!.productAttribute!.productAttributeValues.edges.map(
      (e) => e.node,
    ) as AttributeValue[];
    const existingValue = beforeValues[0];

    await gql
      .mutation(updateAttribute, {
        id: attributeId,
        values: [{ id: existingValue.id, label: "Updated" }] as never,
      })
      .toPromise();

    const detailAfter = await gql.query(getAttributeDetail, { id: attributeId }).toPromise();
    const editAfterValues = detailAfter.data!.productAttribute!.productAttributeValues.edges.map(
      (e) => e.node,
    ) as AttributeValue[];
    const updatedValue = editAfterValues.find((v: AttributeValue) => v.id === existingValue.id);
    expect(updatedValue?.label).toBe("Updated");
  });

  it("Given an existing value, when deleted, then the value is removed from the attribute.", async () => {
    const code = `MV-DEL-${Date.now()}`;
    const createRes = await gql
      .mutation(createAttribute, {
        code,
        name: "Delete Value Attr",
        valueLabels: ["Keep", "Remove"],
      })
      .toPromise();
    const attributeId = createRes.data!.createAttribute.id;

    const detailBefore = await gql.query(getAttributeDetail, { id: attributeId }).toPromise();
    const delBeforeValues = detailBefore.data!.productAttribute!.productAttributeValues.edges.map(
      (e) => e.node,
    ) as AttributeValue[];
    const keepValue = delBeforeValues.find((v: AttributeValue) => v.label === "Keep");

    // Omit "Remove" to delete it.
    await gql
      .mutation(updateAttribute, {
        id: attributeId,
        values: [{ id: keepValue!.id, label: keepValue!.label }] as never,
      })
      .toPromise();

    const detailAfter = await gql.query(getAttributeDetail, { id: attributeId }).toPromise();
    const delAfterValues = detailAfter.data!.productAttribute!.productAttributeValues.edges.map(
      (e) => e.node,
    ) as AttributeValue[];
    expect(delAfterValues).toHaveLength(1);
    expect(delAfterValues[0].label).toBe("Keep");
  });
});
