package com.datadog.iast.model;

import static com.datadog.iast.model.Range.NOT_MARKED;

import datadog.trace.api.iast.VulnerabilityMarks;
import datadog.trace.api.iast.VulnerabilityTypes;
import java.io.File;
import java.util.zip.CRC32;
import javax.annotation.Nonnull;

public interface VulnerabilityType {
  VulnerabilityType WEAK_CIPHER =
      new VulnerabilityTypeImpl(VulnerabilityTypes.WEAK_CIPHER, NOT_MARKED);
  VulnerabilityType WEAK_HASH = new VulnerabilityTypeImpl(VulnerabilityTypes.WEAK_HASH, NOT_MARKED);
  VulnerabilityType INSECURE_COOKIE =
      new VulnerabilityTypeImpl(VulnerabilityTypes.INSECURE_COOKIE, NOT_MARKED);
  VulnerabilityType NO_HTTPONLY_COOKIE =
      new VulnerabilityTypeImpl(VulnerabilityTypes.NO_HTTPONLY_COOKIE, NOT_MARKED);
  VulnerabilityType HSTS_HEADER_MISSING =
      new VulnerabilityTypeImpl(VulnerabilityTypes.HSTS_HEADER_MISSING, NOT_MARKED);
  VulnerabilityType XCONTENTTYPE_HEADER_MISSING =
      new VulnerabilityTypeImpl(VulnerabilityTypes.XCONTENTTYPE_HEADER_MISSING, NOT_MARKED);
  VulnerabilityType NO_SAMESITE_COOKIE =
      new VulnerabilityTypeImpl(VulnerabilityTypes.NO_SAMESITE_COOKIE, NOT_MARKED);

  InjectionType SQL_INJECTION =
      new InjectionTypeImpl(
          VulnerabilityTypes.SQL_INJECTION, VulnerabilityMarks.SQL_INJECTION_MARK, ' ');
  InjectionType COMMAND_INJECTION =
      new InjectionTypeImpl(
          VulnerabilityTypes.COMMAND_INJECTION, VulnerabilityMarks.COMMAND_INJECTION_MARK, ' ');
  InjectionType PATH_TRAVERSAL =
      new InjectionTypeImpl(
          VulnerabilityTypes.PATH_TRAVERSAL,
          VulnerabilityMarks.PATH_TRAVERSAL_MARK,
          File.separatorChar);
  InjectionType LDAP_INJECTION =
      new InjectionTypeImpl(
          VulnerabilityTypes.LDAP_INJECTION, VulnerabilityMarks.LDAP_INJECTION_MARK, ' ');
  InjectionType SSRF =
      new InjectionTypeImpl(VulnerabilityTypes.SSRF, VulnerabilityMarks.SSRF_MARK, ' ');
  InjectionType UNVALIDATED_REDIRECT =
      new InjectionTypeImpl(
          VulnerabilityTypes.UNVALIDATED_REDIRECT,
          VulnerabilityMarks.UNVALIDATED_REDIRECT_MARK,
          ' ');
  VulnerabilityType WEAK_RANDOMNESS =
      new VulnerabilityTypeImpl(VulnerabilityTypes.WEAK_RANDOMNESS, NOT_MARKED);

  InjectionType XPATH_INJECTION =
      new InjectionTypeImpl(
          VulnerabilityTypes.XPATH_INJECTION, VulnerabilityMarks.XPATH_INJECTION_MARK, ' ');

  InjectionType TRUST_BOUNDARY_VIOLATION =
      new InjectionTypeImpl(
          VulnerabilityTypes.TRUST_BOUNDARY_VIOLATION,
          VulnerabilityMarks.TRUST_BOUNDARY_VIOLATION,
          ' ');

  InjectionType XSS =
      new InjectionTypeImpl(VulnerabilityTypes.XSS, VulnerabilityMarks.XSS_MARK, ' ');

  String name();

  /** A bit flag to ignore tainted ranges for this vulnerability. Set to 0 if none. */
  int mark();

  long calculateHash(@Nonnull final Vulnerability vulnerability);

  interface InjectionType extends VulnerabilityType {
    char evidenceSeparator();
  }

  class VulnerabilityTypeImpl implements VulnerabilityType {

    private final String name;

    private final int mark;

    public VulnerabilityTypeImpl(@Nonnull final String name, final int vulnerabilityMark) {
      this.name = name;
      mark = vulnerabilityMark;
    }

    @Override
    public String name() {
      return name;
    }

    @Override
    public int mark() {
      return mark;
    }

    @Override
    public long calculateHash(@Nonnull final Vulnerability vulnerability) {
      CRC32 crc = new CRC32();
      crc.update(name().getBytes());
      final Location location = vulnerability.getLocation();
      if (location != null) {
        crc.update(location.getLine());
        crc.update(location.getPath().getBytes());
        if (location.getLine() <= -1 && location.getMethod() != null) {
          crc.update(location.getMethod().getBytes());
        }
      }
      return crc.getValue();
    }
  }

  class InjectionTypeImpl extends VulnerabilityTypeImpl implements InjectionType {
    private final char evidenceSeparator;

    public InjectionTypeImpl(
        @Nonnull final String name, final int vulnerabilityMark, final char evidenceSeparator) {
      super(name, vulnerabilityMark);
      this.evidenceSeparator = evidenceSeparator;
    }

    @Override
    public char evidenceSeparator() {
      return evidenceSeparator;
    }
  }
}
