import { PkiRegenerateDialog } from "@app/components/Dialog/PkiRegenerateDialog";
import { DynamicForm } from "@app/components/Form/DynamicForm.tsx";
import { useAppStore } from "@app/core/stores/appStore";
import {
  getX25519PrivateKey,
  getX25519PublicKey,
} from "@app/core/utils/x25519";
import type { SecurityValidation } from "@app/validation/config/security.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js";
import { fromByteArray, toByteArray } from "base64-js";
import { Eye, EyeOff } from "lucide-react";
import { useReducer } from "react";
import { securityReducer } from "./securityReducer";

export const Security = () => {
  const { config, setWorkingConfig, setDialogOpen } = useDevice();
  const {
    hasErrors,
    getErrorMessage,
    hasFieldError,
    addError,
    removeError,
    clearErrors,
  } = useAppStore();

  const [state, dispatch] = useReducer(securityReducer, {
    privateKey: fromByteArray(config.security?.privateKey ?? new Uint8Array(0)),
    privateKeyVisible: false,
    adminKeyVisible: false,
    privateKeyBitCount: config.security?.privateKey.length ?? 32,
    adminKeyBitCount: config.security?.adminKey.at(0).length ?? 32,
    publicKey: fromByteArray(config.security?.publicKey ?? new Uint8Array(0)),
    adminKey: fromByteArray(
      config.security?.adminKey.at(0) ?? new Uint8Array(0),
    ),
    privateKeyDialogOpen: false,
  });
  console.log(config.security.adminKey);

  const validateKey = (
    input: string,
    count: number,
    fieldName: "privateKey" | "adminKey",
  ) => {
    try {
      removeError(fieldName);

      if (fieldName === "privateKey" && input === "") {
        addError(fieldName, "Private Key is required");
        return;
      }

      if (fieldName === "adminKey" && input === "") {
        return;
      }

      if (input.length % 4 !== 0) {
        addError(
          fieldName,
          `${fieldName === "privateKey" ? "Private" : "Admin"} Key is required to be a 256 bit pre-shared key (PSK)`,
        );
        return;
      }

      const decoded = toByteArray(input);
      if (decoded.length !== count) {
        addError(fieldName, `Please enter a valid ${count * 8} bit PSK`);
        return;
      }
    } catch (e) {
      console.error(e);
      addError(
        fieldName,
        `Invalid ${fieldName === "privateKey" ? "Private" : "Admin"} Key format`,
      );
    }
  };

  const onSubmit = (data: SecurityValidation) => {
    if (hasErrors()) {
      return;
    }
    console.log(toByteArray(state.adminKey));

    setWorkingConfig(
      new Protobuf.Config.Config({
        payloadVariant: {
          case: "security",
          value: {
            ...data,
            adminKey: [new Uint8Array(0)],
            privateKey: toByteArray(state.privateKey),
            publicKey: toByteArray(state.publicKey),
          },
        },
      }),
    );
  };

  const pkiRegenerate = () => {
    clearErrors();
    const privateKey = getX25519PrivateKey();
    const publicKey = getX25519PublicKey(privateKey);

    dispatch({
      type: "REGENERATE_PRIV_PUB_KEY",
      payload: {
        privateKey: fromByteArray(privateKey),
        publicKey: fromByteArray(publicKey),
      },
    });

    validateKey(
      fromByteArray(privateKey),
      state.privateKeyBitCount,
      "privateKey",
    );
  };

  const privateKeyInputChangeEvent = (
    e: React.ChangeEvent<HTMLInputElement>,
  ) => {
    const privateKeyB64String = e.target.value;
    dispatch({ type: "SET_PRIVATE_KEY", payload: privateKeyB64String });
    validateKey(privateKeyB64String, state.privateKeyBitCount, "privateKey");

    const publicKey = getX25519PublicKey(toByteArray(privateKeyB64String));
    dispatch({ type: "SET_PUBLIC_KEY", payload: fromByteArray(publicKey) });
  };

  const adminKeyInputChangeEvent = (e: React.ChangeEvent<HTMLInputElement>) => {
    const psk = e.currentTarget?.value;
    dispatch({ type: "SET_ADMIN_KEY", payload: psk });
    validateKey(psk, state.privateKeyBitCount, "adminKey");
  };

  const privateKeySelectChangeEvent = (e: string) => {
    const count = Number.parseInt(e);
    dispatch({ type: "SET_PRIVATE_KEY_BIT_COUNT", payload: count });
    validateKey(state.privateKey, count, "privateKey");
  };

  return (
    <>
      <DynamicForm<SecurityValidation>
        onSubmit={onSubmit}
        submitType="onChange"
        defaultValues={{
          ...config.security,
          ...{
            adminKey: state.adminKey,
            privateKey: state.privateKey,
            publicKey: state.publicKey,
            adminChannelEnabled: config.security?.adminChannelEnabled ?? false,
            isManaged: config.security?.isManaged ?? false,
            debugLogApiEnabled: config.security?.debugLogApiEnabled ?? false,
            serialEnabled: config.security?.serialEnabled ?? false,
          },
        }}
        fieldGroups={[
          {
            label: "Security Settings",
            description: "Settings for the Security configuration",
            fields: [
              {
                type: "passwordGenerator",
                name: "privateKey",
                label: "Private Key",
                description: "Used to create a shared key with a remote device",
                bits: [{ text: "256 bit", value: "32", key: "bit256" }],
                validationText: hasFieldError("privateKey")
                  ? getErrorMessage("privateKey")
                  : "",
                devicePSKBitCount: state.privateKeyBitCount,
                inputChange: privateKeyInputChangeEvent,
                selectChange: privateKeySelectChangeEvent,
                hide: !state.privateKeyVisible,
                actionButtons: [
                  {
                    text: "Generate",
                    onClick: () =>
                      dispatch({
                        type: "SHOW_PRIVATE_KEY_DIALOG",
                        payload: true,
                      }),
                    variant: "success",
                  },
                  {
                    text: "Backup Key",
                    onClick: () => setDialogOpen("pkiBackup", true),
                    variant: "subtle",
                  },
                ],
                properties: {
                  value: state.privateKey,
                  action: {
                    icon: state.privateKeyVisible ? EyeOff : Eye,
                    onClick: () =>
                      dispatch({ type: "TOGGLE_PRIVATE_KEY_VISIBILITY" }),
                  },
                },
              },
              {
                type: "text",
                name: "publicKey",
                label: "Public Key",
                disabled: true,
                description:
                  "Sent out to other nodes on the mesh to allow them to compute a shared secret key",
                properties: {
                  value: state.publicKey,
                },
              },
            ],
          },
          {
            label: "Admin Settings",
            description: "Settings for Admin",
            fields: [
              {
                type: "toggle",
                name: "adminChannelEnabled",
                label: "Allow Legacy Admin",
                description:
                  "Allow incoming device control over the insecure legacy admin channel",
              },
              {
                type: "toggle",
                name: "isManaged",
                label: "Managed",
                description:
                  "If true, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless a suitable Remote Admin node has been setup, and the public key stored in the field below.",
              },
              {
                type: "passwordGenerator",
                name: "adminKey",
                label: "Admin Key",
                description:
                  "The public key authorized to send admin messages to this node",
                validationText: hasFieldError("adminKey")
                  ? getErrorMessage("adminKey")
                  : "",
                inputChange: adminKeyInputChangeEvent,
                selectChange: () => {},
                bits: [{ text: "256 bit", value: "32", key: "bit256" }],
                devicePSKBitCount: state.privateKeyBitCount,
                hide: !state.adminKeyVisible,
                actionButtons: [
                  {
                    text: "Generate",
                    variant: "success",
                    onClick: () => {
                      const adminKey = getX25519PrivateKey();
                      dispatch({
                        type: "REGENERATE_ADMIN_KEY",
                        payload: { adminKey: fromByteArray(adminKey) },
                      });
                      validateKey(
                        fromByteArray(adminKey),
                        state.adminKeyBitCount,
                        "adminKey",
                      );
                    },
                  },
                ],
                disabledBy: [
                  { fieldName: "adminChannelEnabled", invert: true },
                ],
                properties: {
                  value: state.adminKey,
                  action: {
                    icon: state.adminKeyVisible ? EyeOff : Eye,
                    onClick: () =>
                      dispatch({ type: "TOGGLE_ADMIN_KEY_VISIBILITY" }),
                  },
                },
              },
            ],
          },
          {
            label: "Logging Settings",
            description: "Settings for Logging",
            fields: [
              {
                type: "toggle",
                name: "debugLogApiEnabled",
                label: "Enable Debug Log API",
                description:
                  "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth",
              },
              {
                type: "toggle",
                name: "serialEnabled",
                label: "Serial Output Enabled",
                description: "Serial Console over the Stream API",
              },
            ],
          },
        ]}
      />
      <PkiRegenerateDialog
        open={state.privateKeyDialogOpen}
        onOpenChange={() =>
          dispatch({ type: "SHOW_PRIVATE_KEY_DIALOG", payload: false })
        }
        onSubmit={pkiRegenerate}
      />
    </>
  );
};
