package library

import (
	"path/filepath"
	"time"

	ftypes "github.com/aquasecurity/fanal/types"

	"github.com/google/wire"

	"github.com/aquasecurity/trivy/pkg/log"
	"github.com/knqyf263/go-version"

	"golang.org/x/xerrors"

	"github.com/aquasecurity/trivy/pkg/types"
)

var SuperSet = wire.NewSet(
	wire.Struct(new(DriverFactory)),
	wire.Bind(new(Factory), new(DriverFactory)),
	NewDetector,
	wire.Bind(new(Operation), new(Detector)),
)

type Operation interface {
	Detect(imageName string, filePath string, created time.Time, pkgs []ftypes.LibraryInfo) (vulns []types.DetectedVulnerability, err error)
}

type Detector struct {
	driverFactory Factory
}

func NewDetector(factory Factory) Detector {
	return Detector{driverFactory: factory}
}

func (d Detector) Detect(_, filePath string, _ time.Time, pkgs []ftypes.LibraryInfo) ([]types.DetectedVulnerability, error) {
	log.Logger.Debugf("Detecting library vulnerabilities, path: %s", filePath)
	driver := d.driverFactory.NewDriver(filepath.Base(filePath))
	if driver == nil {
		return nil, xerrors.New("unknown file type")
	}

	vulns, err := detect(driver, pkgs)
	if err != nil {
		return nil, xerrors.Errorf("failed to scan %s vulnerabilities: %w", driver.Type(), err)
	}

	return vulns, nil
}

func detect(driver Driver, libs []ftypes.LibraryInfo) ([]types.DetectedVulnerability, error) {
	log.Logger.Infof("Detecting %s vulnerabilities...", driver.Type())
	var vulnerabilities []types.DetectedVulnerability
	for _, lib := range libs {
		v, err := version.NewVersion(lib.Library.Version)
		if err != nil {
			log.Logger.Debugf("invalid version, library: %s, version: %s, error: %s\n",
				lib.Library.Name, lib.Library.Version, err)
			continue
		}

		vulns, err := driver.Detect(lib.Library.Name, v)
		if err != nil {
			return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", driver.Type(), err)
		}

		for i := range vulns {
			vulns[i].LayerID = lib.LayerID
		}
		vulnerabilities = append(vulnerabilities, vulns...)
	}

	return vulnerabilities, nil
}
