package buildpacks

import (
	"strings"
	"sync"

	"github.com/google/go-github/v41/github"
	"github.com/xanzy/go-gitlab"
)

type pythonRuntime struct {
	wg sync.WaitGroup
}

func NewPythonRuntime() Runtime {
	return &pythonRuntime{}
}

func (runtime *pythonRuntime) detectPipenvGithub(results chan struct {
	string
	bool
}, directoryContent []*github.RepositoryContent,
) {
	pipfileFound := false
	pipfileLockFound := false
	for i := 0; i < len(directoryContent); i++ {
		name := directoryContent[i].GetName()
		if name == "Pipfile" {
			pipfileFound = true
		} else if name == "Pipfile.lock" {
			pipfileLockFound = true
		}
		if pipfileFound && pipfileLockFound {
			break
		}
	}
	if pipfileFound && pipfileLockFound {
		results <- struct {
			string
			bool
		}{pipenv, true}
	}
	runtime.wg.Done()
}

func (runtime *pythonRuntime) detectPipenvGitlab(results chan struct {
	string
	bool
}, tree []*gitlab.TreeNode,
) {
	pipfileFound := false
	pipfileLockFound := false
	for i := 0; i < len(tree); i++ {
		name := tree[i].Name
		if name == "Pipfile" {
			pipfileFound = true
		} else if name == "Pipfile.lock" {
			pipfileLockFound = true
		}
		if pipfileFound && pipfileLockFound {
			break
		}
	}
	if pipfileFound && pipfileLockFound {
		results <- struct {
			string
			bool
		}{pipenv, true}
	}
	runtime.wg.Done()
}

func (runtime *pythonRuntime) detectPipGithub(results chan struct {
	string
	bool
}, directoryContent []*github.RepositoryContent,
) {
	requirementsTxtFound := false
	for i := 0; i < len(directoryContent); i++ {
		name := directoryContent[i].GetName()
		if name == "requirements.txt" {
			requirementsTxtFound = true
		}
	}
	if requirementsTxtFound {
		results <- struct {
			string
			bool
		}{pip, true}
	}
	runtime.wg.Done()
}

func (runtime *pythonRuntime) detectPipGitlab(results chan struct {
	string
	bool
}, tree []*gitlab.TreeNode,
) {
	requirementsTxtFound := false
	for i := 0; i < len(tree); i++ {
		name := tree[i].Name
		if name == "requirements.txt" {
			requirementsTxtFound = true
		}
	}
	if requirementsTxtFound {
		results <- struct {
			string
			bool
		}{pip, true}
	}
	runtime.wg.Done()
}

func (runtime *pythonRuntime) detectCondaGithub(results chan struct {
	string
	bool
}, directoryContent []*github.RepositoryContent,
) {
	environmentFound := false
	packageListFound := false
	for i := 0; i < len(directoryContent); i++ {
		name := directoryContent[i].GetName()
		if name == "environment.yml" {
			environmentFound = true
			break
		} else if name == "package-list.txt" {
			packageListFound = true
			break
		}
	}
	if environmentFound || packageListFound {
		results <- struct {
			string
			bool
		}{conda, true}
	}
	runtime.wg.Done()
}

func (runtime *pythonRuntime) detectCondaGitlab(results chan struct {
	string
	bool
}, tree []*gitlab.TreeNode,
) {
	environmentFound := false
	packageListFound := false
	for i := 0; i < len(tree); i++ {
		name := tree[i].Name
		if name == "environment.yml" {
			environmentFound = true
			break
		} else if name == "package-list.txt" {
			packageListFound = true
			break
		}
	}
	if environmentFound || packageListFound {
		results <- struct {
			string
			bool
		}{conda, true}
	}
	runtime.wg.Done()
}

func (runtime *pythonRuntime) detectStandaloneGithub(results chan struct {
	string
	bool
}, directoryContent []*github.RepositoryContent,
) {
	pyFound := false
	for i := 0; i < len(directoryContent); i++ {
		name := directoryContent[i].GetName()
		if strings.HasSuffix(name, ".py") {
			pyFound = true
			break
		}
	}
	if pyFound {
		results <- struct {
			string
			bool
		}{standalone, true}
	}
	runtime.wg.Done()
}

func (runtime *pythonRuntime) detectStandaloneGitlab(results chan struct {
	string
	bool
}, tree []*gitlab.TreeNode,
) {
	pyFound := false
	for i := 0; i < len(tree); i++ {
		name := tree[i].Name
		if strings.HasSuffix(name, ".py") {
			pyFound = true
			break
		}
	}
	if pyFound {
		results <- struct {
			string
			bool
		}{standalone, true}
	}
	runtime.wg.Done()
}

func (runtime *pythonRuntime) DetectGithub(
	client *github.Client,
	directoryContent []*github.RepositoryContent,
	owner, name, path string,
	repoContentOptions github.RepositoryContentGetOptions,
	paketo, heroku *BuilderInfo,
) error {
	results := make(chan struct {
		string
		bool
	}, 4)

	runtime.wg.Add(4)
	go runtime.detectPipenvGithub(results, directoryContent)
	go runtime.detectPipGithub(results, directoryContent)
	go runtime.detectCondaGithub(results, directoryContent)
	go runtime.detectStandaloneGithub(results, directoryContent)
	runtime.wg.Wait()
	close(results)

	paketoBuildpackInfo := BuildpackInfo{
		Name:      "Python",
		Buildpack: "gcr.io/paketo-buildpacks/python",
	}
	herokuBuildpackInfo := BuildpackInfo{
		Name:      "Python",
		Buildpack: "heroku/python",
	}

	if len(results) == 0 {
		paketo.Others = append(paketo.Others, paketoBuildpackInfo)
		heroku.Others = append(heroku.Others, herokuBuildpackInfo)
		return nil
	}

	paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
	heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)

	return nil
}

func (runtime *pythonRuntime) DetectGitlab(
	client *gitlab.Client,
	tree []*gitlab.TreeNode,
	repoPath, path, ref string,
	paketo, heroku *BuilderInfo,
) error {
	results := make(chan struct {
		string
		bool
	}, 4)

	runtime.wg.Add(4)
	go runtime.detectPipenvGitlab(results, tree)
	go runtime.detectPipGitlab(results, tree)
	go runtime.detectCondaGitlab(results, tree)
	go runtime.detectStandaloneGitlab(results, tree)
	runtime.wg.Wait()
	close(results)

	paketoBuildpackInfo := BuildpackInfo{
		Name:      "Python",
		Buildpack: "gcr.io/paketo-buildpacks/python",
	}
	herokuBuildpackInfo := BuildpackInfo{
		Name:      "Python",
		Buildpack: "heroku/python",
	}

	if len(results) == 0 {
		paketo.Others = append(paketo.Others, paketoBuildpackInfo)
		heroku.Others = append(heroku.Others, herokuBuildpackInfo)
		return nil
	}

	paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
	heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)

	return nil
}
