import React, { FunctionComponent, useState } from "react";
import { Link, useHistory, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { FormProvider, useForm } from "react-hook-form";
import {
  ActionGroup,
  AlertVariant,
  Button,
  ButtonVariant,
  DropdownItem,
  PageSection,
} from "@patternfly/react-core";

import type PolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/policyRepresentation";
import {
  PolicyDetailsParams,
  toPolicyDetails,
} from "../../routes/PolicyDetails";
import { ViewHeader } from "../../../components/view-header/ViewHeader";
import { KeycloakSpinner } from "../../../components/keycloak-spinner/KeycloakSpinner";
import { useConfirmDialog } from "../../../components/confirm-dialog/ConfirmDialog";
import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
import { FormAccess } from "../../../components/form-access/FormAccess";
import { useAlerts } from "../../../components/alert/Alerts";
import { toAuthorizationTab } from "../../routes/AuthenticationTab";
import { Aggregate } from "./Aggregate";
import { Client } from "./Client";
import { User } from "./User";
import { NameDescription } from "./NameDescription";
import { LogicSelector } from "./LogicSelector";
import { ClientScope, RequiredIdValue } from "./ClientScope";
import { Group, GroupValue } from "./Group";
import { Regex } from "./Regex";
import { Role } from "./Role";
import { Time } from "./Time";
import { JavaScript } from "./JavaScript";

import "./policy-details.css";

type Policy = Omit<PolicyRepresentation, "roles"> & {
  groups?: GroupValue[];
  clientScopes?: RequiredIdValue[];
  roles?: RequiredIdValue[];
};

const COMPONENTS: {
  [index: string]: FunctionComponent;
} = {
  aggregate: Aggregate,
  client: Client,
  user: User,
  "client-scope": ClientScope,
  group: Group,
  regex: Regex,
  role: Role,
  time: Time,
  js: JavaScript,
} as const;

export const isValidComponentType = (value: string) => value in COMPONENTS;

export default function PolicyDetails() {
  const { t } = useTranslation("clients");
  const { id, realm, policyId, policyType } = useParams<PolicyDetailsParams>();
  const history = useHistory();
  const form = useForm({ shouldUnregister: false });
  const { reset, handleSubmit } = form;

  const adminClient = useAdminClient();
  const { addAlert, addError } = useAlerts();

  const [policy, setPolicy] = useState<PolicyRepresentation>();

  useFetch(
    async () => {
      if (policyId) {
        const result = await Promise.all([
          adminClient.clients.findOnePolicy({
            id,
            type: policyType,
            policyId,
          }) as PolicyRepresentation | undefined,
          adminClient.clients.getAssociatedPolicies({
            id,
            permissionId: policyId,
          }),
        ]);

        if (!result[0]) {
          throw new Error(t("common:notFound"));
        }

        return {
          policy: result[0],
          policies: result[1].map((p) => p.id),
        };
      }
      return {};
    },
    ({ policy, policies }) => {
      reset({ ...policy, policies });
      setPolicy(policy);
    },
    []
  );

  const save = async (policy: Policy) => {
    // remove entries that only have the boolean set and no id
    policy.groups = policy.groups?.filter((g) => g.id);
    policy.clientScopes = policy.clientScopes?.filter((c) => c.id);
    policy.roles = policy.roles
      ?.filter((r) => r.id)
      .map((r) => ({ ...r, required: r.required || false }));

    try {
      if (policyId) {
        await adminClient.clients.updatePolicy(
          { id, type: policyType, policyId },
          policy
        );
      } else {
        const result = await adminClient.clients.createPolicy(
          { id, type: policyType },
          policy
        );
        history.push(
          toPolicyDetails({
            realm,
            id,
            policyType,
            policyId: result.id!,
          })
        );
      }
      addAlert(
        t((policyId ? "update" : "create") + "PolicySuccess"),
        AlertVariant.success
      );
    } catch (error) {
      addError("clients:policySaveError", error);
    }
  };

  const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
    titleKey: "clients:deletePolicy",
    messageKey: "clients:deletePolicyConfirm",
    continueButtonLabel: "clients:confirm",
    onConfirm: async () => {
      try {
        await adminClient.clients.delPolicy({
          id,
          policyId,
        });
        addAlert(t("policyDeletedSuccess"), AlertVariant.success);
        history.push(
          toAuthorizationTab({ realm, clientId: id, tab: "policies" })
        );
      } catch (error) {
        addError("clients:policyDeletedError", error);
      }
    },
  });

  if (policyId && !policy) {
    return <KeycloakSpinner />;
  }

  const ComponentType = isValidComponentType(policyType)
    ? COMPONENTS[policyType]
    : COMPONENTS["js"];

  return (
    <>
      <DeleteConfirm />
      <ViewHeader
        titleKey={policyId ? policy?.name! : "clients:createPolicy"}
        dropdownItems={
          policyId
            ? [
                <DropdownItem
                  key="delete"
                  data-testid="delete-policy"
                  onClick={() => toggleDeleteDialog()}
                >
                  {t("common:delete")}
                </DropdownItem>,
              ]
            : undefined
        }
      />
      <PageSection variant="light">
        <FormAccess
          isHorizontal
          onSubmit={handleSubmit(save)}
          role="view-clients"
        >
          <FormProvider {...form}>
            <NameDescription prefix="policy" />
            <ComponentType />
            <LogicSelector />
          </FormProvider>
          <ActionGroup>
            <div className="pf-u-mt-md">
              <Button
                variant={ButtonVariant.primary}
                type="submit"
                data-testid="save"
              >
                {t("common:save")}
              </Button>

              <Button
                variant="link"
                data-testid="cancel"
                component={(props) => (
                  <Link
                    {...props}
                    to={toAuthorizationTab({
                      realm,
                      clientId: id,
                      tab: "policies",
                    })}
                  />
                )}
              >
                {t("common:cancel")}
              </Button>
            </div>
          </ActionGroup>
        </FormAccess>
      </PageSection>
    </>
  );
}
