/*
/*
 * Copyright (c) 2024 Augur AI, Inc.
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. 
 * If a copy of the MPL was not distributed with this file, you can obtain one at https://mozilla.org/MPL/2.0/.
 *
 
 * Copyright (c) 2024 Augur AI, Inc.
 *
 * This file is licensed under the Augur AI Proprietary License.
 *
 * Attribution:
 * This work is based on code from https://github.com/hofstadter-io/hof, licensed under the Apache License 2.0.
 */

package mod

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/go-git/go-billy/v5"
	"github.com/go-git/go-billy/v5/osfs"
	"golang.org/x/mod/module"
	"golang.org/x/mod/semver"

	"github.com/opentofu/opentofu/internal/hof/lib/mod/mvs"
	"github.com/opentofu/opentofu/internal/hof/lib/repos/cache"
)

func (cm *CueMod) SolveMVS(latest bool) error {
	// fmt.Println("solve mvs:", cm.Module)

	rr := &RequirementResolver{ cm, latest, make(map[string]bool) }

	targets := []module.Version{}
	for _, dep := range cm.Replace {
		//fmt.Println("Replace:", dep)
		d := module.Version{ Path: dep.Path, Version: dep.Version }
		targets = append(targets, d)
	}
	for path, ver := range cm.Require {
		// skip any replaced modules
		if _, ok := cm.Replace[path]; ok {
			continue
		}
		//fmt.Println("Require:", path, ver)
		// todo, we probably need to get the lastest of each target before starting MVS here
		// but only when latest == true
		dep := module.Version{ Path: path, Version: ver }
		targets = append(targets, dep)
	}

	//fmt.Println("Targets:")
	//for _, t := range targets {
	//  fmt.Println(t.Path, t.Version)
	//}

	final, err := mvs.BuildList(targets, rr)
	if err != nil {
		return err
	}

	//fmt.Println("Final:")
	//for _, t := range final {
	//  fmt.Println(t.Path, t.Version)
	//}
	cm.BuildList = final

	return nil
}

// type needed by Go's mvs library
type RequirementResolver struct {
	rootMod *CueMod
	latest  bool

	fetched map[string]bool
}

func cmpVersion(v1, v2 string) int {
	if v2 == "" {
		if v1 == "" {
			return 0
		}
		return -1
	}
	if v1 == "" {
		return 1
	}

	return semver.Compare(v1, v2)
}

func (rr *RequirementResolver) Max(v1, v2 string) string {
	x := cmpVersion(v1, v2)
	// fmt.Println("MAX", v1, v2, x)
	if x < 0 {
		return v2
	}
	return v1
}

func (rr *RequirementResolver) Required(m module.Version) ([]module.Version, error) {

	// skip if it is the same as the root module
	if rr.rootMod.Module == m.Path {
		return nil, nil
	}

	var FS billy.Filesystem
	var err error

	// assume local module, can only happen from root module we are working on
	if m.Version == "" {
		// a REPLACE with relative path from the root module
		FS = osfs.New(filepath.Join(rr.rootMod.Basedir, m.Path))
	} else {
		// a REMOTE module
		FS, err = cache.Load(m.Path, m.Version)
	}
	if err != nil {
		return nil, err
	}

	M, err := ReadModule("", FS)
	if err != nil {
		return nil, fmt.Errorf("while reading %s: %w", m, err)
	}

	// fmt.Println("mvs:", m, M.Module)

	deps := []module.Version{}
	for path, ver := range M.Require {
		if rr.latest {
			// possibly extra calls here...?
			_, err = cache.FetchRepoSource(path, "")
			if err != nil {
				return nil, err
			}
			// intentionally not supporting @next or prereleases when updating via all@latest
			nver, err := cache.GetLatestTag(path, false)
			if err != nil {
				return nil, err
			}
			// only update if newer (not if we explicitly require prerelease)
			if semver.Compare(nver, ver) > 0 {
				ver = nver
			}
		}

		// filter a replaced module or same module?
		_, ok1 := rr.rootMod.Replace[path]
		if ok1 || path == rr.rootMod.Module {
			// fmt.Println("mvs-skip:", path, ver)
			continue
		}
		rver, ok2 := rr.rootMod.Require[path]
		// ok2 = ok2 && path == rr.rootMod.Module
		if ok2 && strings.HasPrefix(rver, "v0.0.0-") {
			// fmt.Println("mvs-skip2:", path, ver, rver)
			ver = rver
		}

		dep := module.Version{ Path: path, Version: ver }
		// fmt.Println("mvs-add:", dep)
		deps = append(deps, dep)
	}

	// fmt.Println("deps:", deps)

	return deps, nil
}

/*
	TODO, implement these via the cache
func (rr *RequirementResolver) Upgrade(m module.Version) (module.Version, error) {

	return m, nil
}

func (rr *RequirementResolver) Previous(m module.Version) (module.Version, error) {

	return m, nil
}
*/

