import { useEffect, useState } from 'react';
import { ActionFunctionArgs, useFetcher } from 'react-router-dom';
import { toast } from 'sonner';
import { Button, Checkbox, Radio, Switch, Tooltip } from 'ui-components';

import { getSettingsApiClient, getVulnerabilityApiClient } from '@/api/api';
import {
  ModelNodeIdentifierNodeTypeEnum,
  ModelScanResultsActionRequestScanTypeEnum,
  ModelVulnerabilityScanConfigLanguageLanguageEnum,
  ModelVulnerabilityScanTriggerReq,
  ReportersContainsFilter,
} from '@/api/generated';
import { InfoStandardIcon } from '@/components/icons/common/InfoStandard';
import { ScheduleScanForm } from '@/components/scan-configure-forms/ScheduleScanForm';
import { invalidateAllQueries } from '@/queries';
import { VulnerabilityScanNodeTypeEnum } from '@/types/common';
import { get403Message, getResponseErrors } from '@/utils/403';
import { apiWrapper } from '@/utils/api';
import { isNodeTypeARegistryTagType, isNodeTypeARegistryType } from '@/utils/registry';

const binaries = [
  {
    name: 'Golang binary',
    dependent: ModelVulnerabilityScanConfigLanguageLanguageEnum.Golang,
    value: ModelVulnerabilityScanConfigLanguageLanguageEnum.GolangBinary,
  },
  {
    name: 'Rust binary',
    dependent: ModelVulnerabilityScanConfigLanguageLanguageEnum.Rust,
    value: ModelVulnerabilityScanConfigLanguageLanguageEnum.RustBinary,
  },
];

const getPackages = () => {
  return [
    {
      name: 'OS Packages',
      checked: true,
      value: ModelVulnerabilityScanConfigLanguageLanguageEnum.Base,
    },
    {
      name: 'Java',
      checked: false,
      value: ModelVulnerabilityScanConfigLanguageLanguageEnum.Java,
    },
    {
      name: 'Javascript',
      checked: false,
      value: ModelVulnerabilityScanConfigLanguageLanguageEnum.Javascript,
    },
    {
      name: 'Rust',
      checked: false,
      value: ModelVulnerabilityScanConfigLanguageLanguageEnum.Rust,
    },
    {
      name: 'GoLang',
      checked: false,
      value: ModelVulnerabilityScanConfigLanguageLanguageEnum.Golang,
    },
    {
      name: 'Ruby',
      checked: false,
      value: ModelVulnerabilityScanConfigLanguageLanguageEnum.Ruby,
    },
    {
      name: 'Python',
      checked: false,
      value: ModelVulnerabilityScanConfigLanguageLanguageEnum.Python,
    },
    {
      name: 'PHP',
      checked: false,
      value: ModelVulnerabilityScanConfigLanguageLanguageEnum.Php,
    },
    {
      name: 'Dotnet',
      checked: false,
      value: ModelVulnerabilityScanConfigLanguageLanguageEnum.Dotnet,
    },
  ];
};

export type VulnerabilityScanConfigureFormProps = {
  showAdvancedOptions: boolean;
  showScheduleScanOptions: boolean;
  data:
    | {
        nodes: {
          nodeId: string;
          nodeType:
            | VulnerabilityScanNodeTypeEnum.host
            | VulnerabilityScanNodeTypeEnum.kubernetes_cluster
            | VulnerabilityScanNodeTypeEnum.registry
            | VulnerabilityScanNodeTypeEnum.container
            | VulnerabilityScanNodeTypeEnum.imageTag
            | VulnerabilityScanNodeTypeEnum.pod;
        }[];
      }
    | {
        nodes: {
          nodeId: string;
          nodeType: VulnerabilityScanNodeTypeEnum.image;
        }[];
        images: string[];
      };

  onSuccess: (data?: { nodeType: string; bulkScanId: string }) => void;
  onCancel?: () => void;
};

export type ScanActionReturnType = {
  message?: string;
  success: boolean;
  data?: {
    nodeType: string;
    bulkScanId: string;
  };
};
const shouldSetPriorityScan = (nodeType: VulnerabilityScanNodeTypeEnum) =>
  nodeType !== VulnerabilityScanNodeTypeEnum.kubernetes_cluster;

export const scanVulnerabilityApiAction = async ({
  request,
}: ActionFunctionArgs): Promise<ScanActionReturnType | null> => {
  const formData = await request.formData();
  const nodeIds = formData.get('_nodeIds')?.toString().split(',') ?? [];
  const _images = formData.get('_images')?.toString().split(',') ?? [];
  const nodeTypes = formData.get('_nodeTypes')?.toString().split(',') ?? [];

  const imageTag = formData.get('imageTag')?.toString() ?? '';
  const packages = formData.getAll('packages');
  const binaries = formData.getAll('binaries');
  const scheduleOn = formData.get('scheduleOn') === 'on';
  const scanImmediately = formData.get('scanImmediately') === 'on';
  const scheduleDescription = formData.get('scheduleDescription');
  const scheduleCron = `0 ${formData.get('scheduleCron')}`;

  const isPriorityScan = formData.get('isPriorityScan') === 'on';
  const scanDeepfenceSystem = formData.get('scanDeepfenceSystem') === 'on';

  const getNodeType = (nodeType: VulnerabilityScanNodeTypeEnum | 'container_image') => {
    let _nodeType = nodeType as ModelNodeIdentifierNodeTypeEnum;
    if (
      nodeType === VulnerabilityScanNodeTypeEnum.imageTag ||
      nodeType === 'container_image'
    ) {
      _nodeType = VulnerabilityScanNodeTypeEnum.image;
    } else if (nodeType === VulnerabilityScanNodeTypeEnum.kubernetes_cluster) {
      _nodeType = 'cluster';
    } else if (nodeType === VulnerabilityScanNodeTypeEnum.image) {
      _nodeType = 'registry';
    }
    return _nodeType;
  };

  const createFilter = (
    nodeType: VulnerabilityScanNodeTypeEnum,
  ): ReportersContainsFilter['filter_in'] => {
    if (nodeType === VulnerabilityScanNodeTypeEnum.image) {
      if (imageTag !== '') {
        return {
          docker_image_name: _images,
          docker_image_tag: [imageTag],
        };
      } else {
        return {
          docker_image_name: _images,
        };
      }
    } else if (nodeType === VulnerabilityScanNodeTypeEnum.registry) {
      if (imageTag !== '') {
        return {
          docker_image_tag: [imageTag],
        };
      }
    }
    return null;
  };

  const packagesSelected = packages.map((pkg) => ({
    language: pkg as unknown as ModelVulnerabilityScanConfigLanguageLanguageEnum,
  }));
  if (binaries.length) {
    (binaries as ModelVulnerabilityScanConfigLanguageLanguageEnum[]).forEach((binary) => {
      packagesSelected.push({
        language: binary,
      });
    });
  }
  const nodeType = nodeTypes[0];
  let filter_in = null;
  if (
    nodeType === VulnerabilityScanNodeTypeEnum.image ||
    nodeType === VulnerabilityScanNodeTypeEnum.registry
  ) {
    filter_in = createFilter(nodeType);
  }

  const requestBody: ModelVulnerabilityScanTriggerReq = {
    filters: {
      cloud_account_scan_filter: { filter_in: null },
      kubernetes_cluster_scan_filter: { filter_in: null },
      container_scan_filter: { filter_in: null },
      host_scan_filter: { filter_in: null },
      image_scan_filter: {
        filter_in,
      },
    },
    is_priority: isPriorityScan,
    node_ids: nodeIds.map((nodeId, index) => ({
      node_id: nodeId,
      node_type: getNodeType(
        nodeTypes[index] as VulnerabilityScanNodeTypeEnum,
      ) as ModelNodeIdentifierNodeTypeEnum,
    })),
    scan_config: packagesSelected,
    deepfence_system_scan: scanDeepfenceSystem,
  };

  let scanResponse = {
    success: true,
    data: {
      bulkScanId: '',
      nodeType,
    },
  };
  if (!scheduleOn || scanImmediately) {
    const startVulnerabilityScanApi = apiWrapper({
      fn: getVulnerabilityApiClient().startVulnerabilityScan,
    });

    const startVulnerabilityScanResponse = await startVulnerabilityScanApi({
      modelVulnerabilityScanTriggerReq: requestBody,
    });

    if (!startVulnerabilityScanResponse.ok) {
      if (
        startVulnerabilityScanResponse.error.response.status === 400 ||
        startVulnerabilityScanResponse.error.response.status === 409
      ) {
        const { message } = await getResponseErrors(startVulnerabilityScanResponse.error);
        return {
          success: false,
          message,
        };
      } else if (startVulnerabilityScanResponse.error.response.status === 403) {
        const message = await get403Message(startVulnerabilityScanResponse.error);
        return {
          success: false,
          message,
        };
      } else if (startVulnerabilityScanResponse.error.response.status >= 500) {
        console.error(startVulnerabilityScanResponse.error);
        return {
          success: false,
          message: 'Something went wrong, please try again later.',
        };
      }
      throw startVulnerabilityScanResponse.error;
    }
    scanResponse = {
      success: true,
      data: {
        bulkScanId: startVulnerabilityScanResponse.value.bulk_scan_id,
        nodeType, // for onboard page redirection
      },
    };
  }

  if (scheduleOn) {
    const addScheduledTaskApi = apiWrapper({
      fn: getSettingsApiClient().addScheduledTask,
    });
    const scheduleResponse = await addScheduledTaskApi({
      modelAddScheduledTaskRequest: {
        ...requestBody,
        benchmark_types: null,
        action: ModelScanResultsActionRequestScanTypeEnum.VulnerabilityScan,
        cron_expr: scheduleCron,
        description: scheduleDescription?.toString(),
      },
    });
    if (!scheduleResponse.ok) {
      if (
        scheduleResponse.error.response.status === 400 ||
        scheduleResponse.error.response.status === 409
      ) {
        const { message } = await getResponseErrors(scheduleResponse.error);
        return {
          success: false,
          message,
        };
      } else if (scheduleResponse.error.response.status === 403) {
        const message = await get403Message(scheduleResponse.error);
        return {
          success: false,
          message,
        };
      }
      throw scheduleResponse.error;
    }
  }

  // schedule scan
  if (scheduleOn && scanImmediately) {
    toast.success('Scan started and scheduled successfully');
  } else if (scheduleOn) {
    toast.success('Scan scheduled successfully');
  } else {
    toast.success('Scan started successfully');
  }

  invalidateAllQueries();
  return scanResponse;
};

const ScanDeepfenceSystem = () => {
  return (
    <div className="mt-6 flex items-center gap-x-1">
      <Switch
        label="Scan selected Deepfence images/containers"
        name="scanDeepfenceSystem"
      />
      <Tooltip
        content="If the resources you have selected for the scan include Deepfence images/containers, you can enable this option to scan them. Deepfence images/containers are not scanned by default."
        triggerAsChild
      >
        <span className="w-4 h-4">
          <InfoStandardIcon />
        </span>
      </Tooltip>
    </div>
  );
};

const getScanDeepfenceSystemComponentsConfig = ({
  nodes,
}: {
  nodes: {
    nodeType: VulnerabilityScanNodeTypeEnum;
  }[];
}) => {
  const response: {
    show: boolean;
    value: '' | 'on';
  } = {
    show: false,
    value: '',
  };
  const isOptionVisible = nodes.some(({ nodeType }) => {
    return (
      nodeType === VulnerabilityScanNodeTypeEnum.container ||
      nodeType === VulnerabilityScanNodeTypeEnum.pod ||
      nodeType === VulnerabilityScanNodeTypeEnum.kubernetes_cluster
    );
  });
  if (isOptionVisible) {
    response.show = true;
    return response;
  }

  const shouldDefaultToOn = nodes.some(({ nodeType }) => {
    return (
      nodeType === VulnerabilityScanNodeTypeEnum.registry ||
      nodeType === VulnerabilityScanNodeTypeEnum.imageTag ||
      nodeType === VulnerabilityScanNodeTypeEnum.image ||
      nodeType === ('container_image' as VulnerabilityScanNodeTypeEnum)
    );
  });
  if (shouldDefaultToOn) {
    response.show = false;
    response.value = 'on';
    return response;
  }

  const shouldDefaultToEmpty = nodes.some(({ nodeType }) => {
    return nodeType === VulnerabilityScanNodeTypeEnum.host;
  });

  if (shouldDefaultToEmpty) {
    response.show = false;
    response.value = '';
    return response;
  }
};

const VulnerabilityScanAdvanceOptionForm = ({ nodeType }: { nodeType: string }) => {
  const [imageTag, setImageTag] = useState('latest');
  return (
    <div>
      {isNodeTypeARegistryType(nodeType) && !isNodeTypeARegistryTagType(nodeType) && (
        <h6 className={'text-md font-medium dark:text-white mt-6'}>Advanced Options</h6>
      )}

      <div className="mt-4 flex flex-col gap-y-6">
        {isNodeTypeARegistryType(nodeType) && !isNodeTypeARegistryTagType(nodeType) && (
          <Radio
            name="imageTag"
            value={imageTag}
            options={[
              { label: 'Scan last pushed tag', value: 'recent' },
              { label: 'Scan by "latest" tag', value: 'latest' },
              { label: 'Scan all image tags', value: 'all' },
            ]}
            onValueChange={(value) => {
              setImageTag(value);
            }}
          />
        )}
      </div>
    </div>
  );
};

export const VulnerabilityScanConfigureForm = ({
  data,
  onSuccess,
  onCancel,
  showAdvancedOptions: wantAdvanceOptions,
  showScheduleScanOptions,
}: VulnerabilityScanConfigureFormProps) => {
  const [packageTypes, setPackageTypes] = useState(() => getPackages());
  const [isSelectAll, setIsSelectAll] = useState(false);
  const fetcher = useFetcher<ScanActionReturnType>();
  const { state, data: fetcherData } = fetcher;

  const binaryPackagesToShow = binaries.filter((binaryPackage) => {
    return packageTypes.find(
      (packageType) =>
        packageType.checked === true && packageType.value === binaryPackage.dependent,
    );
  });

  useEffect(() => {
    let data = undefined;
    if (fetcherData?.success && state === 'idle') {
      if (fetcher.data) {
        data = fetcher.data.data;
      }
      onSuccess(data);
    }
  }, [fetcherData, state]);

  useEffect(() => {
    const allChecked = packageTypes.filter((pkg) => pkg.checked === true);
    if (allChecked.length === packageTypes.length) {
      setIsSelectAll(true);
    } else {
      setIsSelectAll(false);
    }
  }, [packageTypes]);

  // select all switch
  const onSwitchChange = (checked: boolean) => {
    setIsSelectAll(checked);
    if (checked) {
      const newPkgs = packageTypes.map((pkg) => {
        pkg.checked = true;
        return pkg;
      });
      setPackageTypes(newPkgs);
    } else {
      const newPkgs = packageTypes.map((pkg) => {
        pkg.checked = false;
        if (pkg.value === ModelVulnerabilityScanConfigLanguageLanguageEnum.Base) {
          pkg.checked = true;
        }
        return pkg;
      });
      setPackageTypes(newPkgs);
    }
  };

  // in case of registry or kubernetes_cluster scan nodeType will be always be of same type
  const nodeType = data.nodes[0].nodeType;
  const isNodeTypeRegistryType =
    nodeType === 'registry' || isNodeTypeARegistryType(nodeType);

  const scanDeepfenceSystemComponentsConfig =
    getScanDeepfenceSystemComponentsConfig(data);
  return (
    <fetcher.Form
      className="flex flex-col"
      method="post"
      action="/data-component/scan/vulnerability"
    >
      <input
        type="text"
        name="_nodeIds"
        hidden
        readOnly
        value={data.nodes.map((node) => node.nodeId).join(',')}
      />
      <input
        type="text"
        name="_nodeTypes"
        readOnly
        hidden
        value={data.nodes.map((node) => node.nodeType).join(',')}
      />
      {'images' in data && (
        <input type="text" name="_images" hidden readOnly value={data.images.join(',')} />
      )}
      <h6 className={'text-p3 text-text-text-and-icon'}>Select packages</h6>
      <div className="mt-2">
        <Switch
          label="Select All"
          name="selectAll"
          onCheckedChange={onSwitchChange}
          checked={isSelectAll}
        />
      </div>

      <div className="flex flex-row mt-4 gap-x-8 gap-y-4 flex-wrap">
        {packageTypes.map((pkg) => {
          return (
            <Checkbox
              label={pkg.name}
              key={pkg.name}
              name="packages"
              checked={pkg.checked}
              value={pkg.value}
              onCheckedChange={(checked: boolean) => {
                if (pkg.value === ModelVulnerabilityScanConfigLanguageLanguageEnum.Base)
                  return;
                setPackageTypes((prevPackageTypes) => {
                  const prevPackageType = prevPackageTypes.find(
                    (packageType) => packageType.value === pkg.value,
                  )!;
                  prevPackageType.checked = checked;
                  return [...prevPackageTypes];
                });
              }}
            />
          );
        })}
      </div>
      {binaryPackagesToShow.length > 0 ? (
        <>
          <h6 className={'mt-8 text-p3 text-text-text-and-icon'}>
            Select binaries
            {!isNodeTypeRegistryType && (
              <span className="ml-1 text-p4 text-text-text-and-icon">
                (binaries scan will consume higher memory)
              </span>
            )}
          </h6>
          <div className="flex mt-4 flex-col  gap-y-4">
            {binaryPackagesToShow.map((binaryPackage) => {
              return (
                <Checkbox
                  label={binaryPackage.name}
                  key={binaryPackage.name}
                  name="binaries"
                  value={binaryPackage.value}
                  defaultChecked={isNodeTypeRegistryType}
                />
              );
            })}
          </div>
        </>
      ) : null}

      {wantAdvanceOptions && <VulnerabilityScanAdvanceOptionForm nodeType={nodeType} />}
      {shouldSetPriorityScan(nodeType as VulnerabilityScanNodeTypeEnum) ? (
        <div className="flex flex-col gap-y-2 mt-4">
          <h6 className={'text-p3 text-text-text-and-icon'}>Priority scan</h6>
          <Checkbox name="isPriorityScan" label="Priority scan" />
        </div>
      ) : null}

      {showScheduleScanOptions && <ScheduleScanForm />}

      {scanDeepfenceSystemComponentsConfig?.show ? (
        <ScanDeepfenceSystem />
      ) : (
        <input
          hidden
          name="scanDeepfenceSystem"
          value={scanDeepfenceSystemComponentsConfig?.value || ''}
        />
      )}

      {fetcherData?.message && (
        <p className="mt-4 text-status-error text-p7">{fetcherData.message}</p>
      )}

      <div className="flex gap-3 mt-14">
        <Button disabled={state !== 'idle'} loading={state !== 'idle'} type="submit">
          Start Scan
        </Button>
        {onCancel ? (
          <Button type="button" variant="outline" onClick={() => onCancel?.()}>
            Cancel
          </Button>
        ) : null}
      </div>
    </fetcher.Form>
  );
};
