import { PkiRegenerateDialog } from "@app/components/Dialog/PkiRegenerateDialog";
import { DynamicForm } from "@app/components/Form/DynamicForm.tsx";
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 { useState } from "react";

export const Security = (): JSX.Element => {
  const { config, nodes, hardware, setWorkingConfig, setDialogOpen } = useDevice();

  const [privateKey, setPrivateKey] = useState<string>(
    fromByteArray(config.security?.privateKey ?? new Uint8Array(0)),
  );
  const [privateKeyVisible, setPrivateKeyVisible] = useState<boolean>(false);
  const [privateKeyBitCount, setPrivateKeyBitCount] = useState<number>(
    config.security?.privateKey.length ?? 32,
  );
  const [privateKeyValidationText, setPrivateKeyValidationText] =
    useState<string>();
  const [publicKey, setPublicKey] = useState<string>(
    fromByteArray(config.security?.publicKey ?? new Uint8Array(0)),
  );
  const [adminKey, setAdminKey] = useState<string>(
    fromByteArray(config.security?.adminKey[0] ?? new Uint8Array(0)),
  );
  const [adminKeyValidationText, setAdminKeyValidationText] =
    useState<string>();
  const [privateKeyDialogOpen, setPrivateKeyDialogOpen] = useState<boolean>(false);

  const onSubmit = (data: SecurityValidation) => {
    if (privateKeyValidationText || adminKeyValidationText) return;

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

  const validateKey = (
    input: string,
    count: number,
    setValidationText: (
      value: React.SetStateAction<string | undefined>,
    ) => void,
  ) => {
    try {
      if (input.length % 4 !== 0 || toByteArray(input).length !== count) {
        setValidationText(`Please enter a valid ${count * 8} bit PSK.`);
      } else {
        setValidationText(undefined);
      }
    } catch (e) {
      console.error(e);
      setValidationText(`Please enter a valid ${count * 8} bit PSK.`);
    }
  };

  const privateKeyClickEvent = () => {
    setPrivateKeyDialogOpen(true);
  };

  const pkiBackupClickEvent = () => {
    setDialogOpen("pkiBackup", true);
  }

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

    setPrivateKey(fromByteArray(privateKey));
    setPublicKey(fromByteArray(publicKey));
    validateKey(
      fromByteArray(privateKey),
      privateKeyBitCount,
      setPrivateKeyValidationText,
    );

    setPrivateKeyDialogOpen(false);
  };

  const privateKeyInputChangeEvent = (
    e: React.ChangeEvent<HTMLInputElement>,
  ) => {
    const privateKeyB64String = e.target.value;
    setPrivateKey(privateKeyB64String);
    validateKey(
      privateKeyB64String,
      privateKeyBitCount,
      setPrivateKeyValidationText,
    );

    const publicKey = getX25519PublicKey(toByteArray(privateKeyB64String));
    setPublicKey(fromByteArray(publicKey));
  };

  const adminKeyInputChangeEvent = (e: React.ChangeEvent<HTMLInputElement>) => {
    const psk = e.currentTarget?.value;
    setAdminKey(psk);
    validateKey(psk, privateKeyBitCount, setAdminKeyValidationText);
  };

  const privateKeySelectChangeEvent = (e: string) => {
    const count = Number.parseInt(e);
    setPrivateKeyBitCount(count);
    validateKey(privateKey, count, setPrivateKeyValidationText);
  };

  return (
    <>
      <DynamicForm<SecurityValidation>
        onSubmit={onSubmit}
        submitType="onChange"
        defaultValues={{
          ...config.security,
          ...{
            adminKey: adminKey,
            privateKey: privateKey,
            publicKey: 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: privateKeyValidationText,
                devicePSKBitCount: privateKeyBitCount,
                inputChange: privateKeyInputChangeEvent,
                selectChange: privateKeySelectChangeEvent,
                hide: !privateKeyVisible,
                actionButtons: [
                  {
                    text: "Generate",
                    onClick: privateKeyClickEvent,
                    variant: "success",
                  },
                  {
                    text: "Backup Key",
                    onClick: pkiBackupClickEvent,
                    variant: "subtle",
                  },
                ],
                properties: {
                  value: privateKey,
                  action: {
                    icon: privateKeyVisible ? EyeOff : Eye,
                    onClick: () => setPrivateKeyVisible(!privateKeyVisible),
                  },
                },
              },
              {
                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: 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: "text",
                name: "adminKey",
                label: "Admin Key",
                description:
                  "The public key authorized to send admin messages to this node",
                validationText: adminKeyValidationText,
                inputChange: adminKeyInputChangeEvent,
                disabledBy: [
                  { fieldName: "adminChannelEnabled", invert: true },
                ],
                properties: {
                  value: adminKey,
                },
              },
            ],
          },
          {
            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={privateKeyDialogOpen}
        onOpenChange={() => setPrivateKeyDialogOpen(false)}
        onSubmit={() => pkiRegenerate()}
      />
    </>
  );
};
