package common

import (
	"encoding/json"
	"fmt"
	"io"
	"slices"

	"src.opensuse.org/autogits/common/gitea-generated/client/repository"
	"src.opensuse.org/autogits/common/gitea-generated/models"
)

//go:generate mockgen -source=maintainership.go -destination=mock/maintainership.go -typed

type MaintainershipData interface {
	ListProjectMaintainers() []string
	ListPackageMaintainers(pkg string) []string

	IsApproved(pkg string, reviews []*models.PullReview) bool
}

const ProjectKey = ""
const ProjectFileKey = "_project"

type MaintainershipMap struct {
	Data         map[string][]string
	IsDir        bool
	FetchPackage func(string) ([]byte, error)
}

func parseMaintainershipData(data []byte) (*MaintainershipMap, error) {
	maintainers := &MaintainershipMap{
		Data: make(map[string][]string),
	}
	if err := json.Unmarshal(data, &maintainers.Data); err != nil {
		return nil, err
	}

	return maintainers, nil
}

func FetchProjectMaintainershipData(gitea GiteaMaintainershipReader, org, prjGit, branch string) (*MaintainershipMap, error) {
	data, _, err := gitea.FetchMaintainershipDirFile(org, prjGit, branch, ProjectFileKey)
	dir := true
	if err != nil || data == nil {
		dir = false
		if _, notFound := err.(*repository.RepoGetRawFileNotFound); !notFound {
			return nil, err
		}
		data, _, err = gitea.FetchMaintainershipFile(org, prjGit, branch)
		if err != nil || data == nil {
			if _, notFound := err.(*repository.RepoGetRawFileNotFound); !notFound {
				return nil, err
			}

			// no mainatiners
			data = []byte("{}")
		}
	}

	m, err := parseMaintainershipData(data)
	if m != nil {
		m.IsDir = dir
		m.FetchPackage = func(pkg string) ([]byte, error) {
			data , _, err := gitea.FetchMaintainershipDirFile(org, prjGit, branch, pkg)
			return data, err
		}
	}
	return m, err
}

func (data *MaintainershipMap) ListProjectMaintainers() []string {
	if data == nil {
		return nil
	}

	m, found := data.Data[ProjectKey]
	if !found {
		return nil
	}

	return m
}

func parsePkgDirData(pkg string, data []byte) []string {
	m := make(map[string][]string)
	if err := json.Unmarshal(data, &m); err != nil {
		return nil
	}

	pkgMaintainers, found := m[pkg]
	if !found {
		return nil
	}
	return pkgMaintainers
}

func (data *MaintainershipMap) ListPackageMaintainers(pkg string) []string {
	if data == nil {
		return nil
	}

	pkgMaintainers, found := data.Data[pkg]
	if !found && data.IsDir {
		pkgData, err := data.FetchPackage(pkg)
		if err == nil {
			pkgMaintainers = parsePkgDirData(pkg, pkgData)
			if len(pkgMaintainers) > 0 {
				data.Data[pkg] = pkgMaintainers
			}
		}
	}
	prjMaintainers := data.ListProjectMaintainers()

prjMaintainer:
	for _, prjm := range prjMaintainers {
		for i := range pkgMaintainers {
			if pkgMaintainers[i] == prjm {
				continue prjMaintainer
			}
		}
		pkgMaintainers = append(pkgMaintainers, prjm)
	}

	return pkgMaintainers
}

func (data *MaintainershipMap) IsApproved(pkg string, reviews []*models.PullReview) bool {
	reviewers, found := data.Data[pkg]
	if !found {
		if pkg != ProjectKey && data.IsDir {
			r, err := data.FetchPackage(pkg)
			if err != nil {
				return false
			}
			reviewers = parsePkgDirData(pkg, r)
			data.Data[pkg] = reviewers
		} else {
			return true
		}
	}

	if len(reviewers) == 0 {
		return true
	}

	for _, review := range reviews {
		if !review.Stale && review.State == ReviewStateApproved && slices.Contains(reviewers, review.User.UserName) {
			return true
		}

	}
	return false
}

func (data *MaintainershipMap) WriteMaintainershipFile(writer io.StringWriter) error {
	if data.IsDir {
		return fmt.Errorf("Not implemented")
	}

	writer.WriteString("{\n")

	if d, ok := data.Data[""]; ok {
		eol := ","
		if len(data.Data) == 1 {
			eol = ""
		}
		slices.Sort(d)
		str, _ := json.Marshal(d)
		writer.WriteString(fmt.Sprintf("    \"\": %s%s\n", string(str), eol))
	}

	keys := make([]string, len(data.Data))
	i := 0
	for pkg := range data.Data {
		if pkg == "" {
			continue
		}
		keys[i] = pkg
		i++
	}
	if len(keys) >= i {
		keys = slices.Delete(keys, i, len(keys))
	}
	slices.Sort(keys)
	for i, pkg := range(keys) {
		eol := ","
		if i == len(keys)-1 {
			eol = ""
		}
		maintainers := data.Data[pkg]
		slices.Sort(maintainers)
		pkgStr, _ := json.Marshal(pkg)
		maintainersStr, _ := json.Marshal(maintainers)
		writer.WriteString(fmt.Sprintf("    %s: %s%s\n", pkgStr, maintainersStr, eol))
	}

	writer.WriteString("}\n")
	return nil
}
