# frozen_string_literal: true

require 'semver_dialects/version'
require 'semver_dialects/semantic_version/version_translator'
require 'semver_dialects/semantic_version/version_parser'
require 'semver_dialects/semantic_version/version_range'
require 'deb_version'

module SemverDialects
  # Captures all errors that could be possibly raised
  class Error < StandardError
    def initialize(msg)
      super(msg)
    end
  end

  class UnsupportedPackageTypeError < Error
    def initialize(pkgType)
      @pkgType = pkgType
    end

    def message
      "unsupported package type '#{@pkgType}'"
    end
  end

  class InvalidVersionError < Error
    def initialize(raw_version)
      @raw_version = raw_version
    end

    def message
      "invalid version '#{@raw_version}'"
    end
  end

  class InvalidConstraintError < Error
    def initialize(raw_constraint)
      @raw_constraint = raw_constraint
    end

    def message
      "invalid constraint '#{@raw_constraint}'"
    end
  end

  # A utiltity module that helps with version matching
  module VersionChecker
    def self.version_translate(typ, version_string)
      case typ
      when 'maven'
        VersionTranslator.translate_maven(version_string)
      when 'npm'
        VersionTranslator.translate_npm(version_string)
      when 'conan'
        VersionTranslator.translate_conan(version_string)
      when 'nuget'
        VersionTranslator.translate_nuget(version_string)
      when 'go'
        VersionTranslator.translate_go(version_string)
      when 'gem'
        VersionTranslator.translate_gem(version_string)
      when 'pypi'
        VersionTranslator.translate_pypi(version_string)
      when 'packagist'
        VersionTranslator.translate_packagist(version_string)
      else
        raise UnsupportedPackageTypeError, typ
      end
    end

    # Determines if a version of a given package type satisfies a constraint.
    #
    # On normal execution, this method might raise the following exceptions:
    #
    # - UnsupportedPackageTypeError if the package type is not supported
    # - InvalidVersionError if the version is invalid
    # - InvalidConstraintError if the constraint is invalid or contains invalid versions
    #
    def self.version_sat?(typ, raw_ver, raw_constraint)
      # os package versions are handled very differently from application package versions
      return os_pkg_version_sat?(typ, raw_ver, raw_constraint) if os_purl_type?(typ)

      # build an interval that only contains the version
      version = VersionCut.new(raw_ver)
      version_as_interval = VersionInterval.new(IntervalType::LEFT_CLOSED | IntervalType::RIGHT_CLOSED, version, version)

      constraint = VersionRange.new(false)
      version_translate(typ, raw_constraint).each do |version_interval_str|
        constraint << VersionParser.parse(version_interval_str)
      end

      constraint.overlaps_with?(version_as_interval)
    end

    def self.os_purl_type?(typ)
      ['deb', 'rpm', 'apk'].include?(typ)
    end

    def self.os_pkg_version_sat?(typ, raw_ver, raw_constraint)
      if typ == 'deb'
        # we only support the less than operator, because that's the only one currently output
        # by the advisory exporter for operating system packages.
        raise SemverDialects::InvalidConstraintError, raw_constraint unless raw_constraint[0] == '<'

        v1 = DebVersion.new(raw_ver)
        v2 = DebVersion.new(raw_constraint[1..-1])

        return v1 < v2
      end
    end
  end
end
