package python

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"

	"github.com/anchore/syft/syft/artifact"
	"github.com/anchore/syft/syft/file"
	"github.com/anchore/syft/syft/pkg"
	"github.com/anchore/syft/syft/pkg/cataloger/generic"
)

type pipfileLock struct {
	Meta struct {
		Hash struct {
			Sha256 string `json:"sha256"`
		} `json:"hash"`
		PipfileSpec int `json:"pipfile-spec"`
		Requires    struct {
			PythonVersion string `json:"python_version"`
		} `json:"requires"`
		Sources []struct {
			Name      string `json:"name"`
			URL       string `json:"url"`
			VerifySsl bool   `json:"verify_ssl"`
		} `json:"sources"`
	} `json:"_meta"`
	Default map[string]pipfileLockDependency `json:"default"`
	Develop map[string]pipfileLockDependency `json:"develop"`
}

type pipfileLockDependency struct {
	Hashes  []string `json:"hashes"`
	Version string   `json:"version"`
	Index   string   `json:"index"`
}

var _ generic.Parser = parsePipfileLock

// parsePipfileLock is a parser function for Pipfile.lock contents, returning "Default" python packages discovered.
func parsePipfileLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
	pkgs := make([]pkg.Package, 0)
	dec := json.NewDecoder(reader)

	for {
		var lock pipfileLock
		if err := dec.Decode(&lock); errors.Is(err, io.EOF) {
			break
		} else if err != nil {
			return nil, nil, fmt.Errorf("failed to parse Pipfile.lock file: %w", err)
		}
		sourcesMap := map[string]string{}
		for _, source := range lock.Meta.Sources {
			sourcesMap[source.Name] = source.URL
		}
		for name, pkgMeta := range lock.Default {
			var index string
			if pkgMeta.Index != "" {
				index = sourcesMap[pkgMeta.Index]
			} else {
				// https://pipenv.pypa.io/en/latest/indexes.html
				index = "https://pypi.org/simple"
			}
			version := strings.TrimPrefix(pkgMeta.Version, "==")
			pkgs = append(pkgs, newPackageForIndexWithMetadata(name, version, pkg.PythonPipfileLockEntry{Index: index, Hashes: pkgMeta.Hashes}, reader.Location))
		}
	}

	pkg.Sort(pkgs)

	return pkgs, nil, nil
}
