package deb

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/DisposaBoy/JsonConfigReader"
	"github.com/aptly-dev/aptly/pgp"
	"github.com/aptly-dev/aptly/utils"
)

// UploadersRule is single rule of format: what packages can group or key upload
type UploadersRule struct {
	Condition         string       `json:"condition"`
	Allow             []string     `json:"allow"`
	Deny              []string     `json:"deny"`
	CompiledCondition PackageQuery `json:"-" codec:"-"`
}

func (u UploadersRule) String() string {
	b, _ := json.Marshal(u)
	return string(b)
}

// Uploaders is configuration of restrictions for .changes file importing
type Uploaders struct {
	Groups map[string][]string `json:"groups"`
	Rules  []UploadersRule     `json:"rules"`
}

func (u *Uploaders) String() string {
	b, _ := json.Marshal(u)
	return string(b)
}

// NewUploadersFromFile loads Uploaders structue from .json file
func NewUploadersFromFile(path string) (*Uploaders, error) {
	uploaders := &Uploaders{}
	f, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("error loading uploaders file: %s", err)
	}
	defer func() { _ = f.Close() }()

	err = json.NewDecoder(JsonConfigReader.New(f)).Decode(&uploaders)
	if err != nil {
		return nil, fmt.Errorf("error loading uploaders file: %s", err)
	}

	return uploaders, nil
}

func (u *Uploaders) expandGroupsInternal(items []string, trail []string) []string {
	result := []string{}

	for _, item := range items {
		// stop infinite recursion
		if utils.StrSliceHasItem(trail, item) {
			continue
		}

		group, ok := u.Groups[item]
		if !ok {
			result = append(result, item)
		} else {
			newTrail := append([]string(nil), trail...)
			result = append(result, u.expandGroupsInternal(group, append(newTrail, item))...)
		}
	}

	return result
}

// ExpandGroups expands list of keys/groups into list of keys
func (u *Uploaders) ExpandGroups(items []string) []string {
	result := u.expandGroupsInternal(items, []string{})

	return utils.StrSliceDeduplicate(result)
}

// IsAllowed checks whether listed keys are allowed to upload given .changes file
func (u *Uploaders) IsAllowed(changes *Changes) error {
	for _, rule := range u.Rules {
		if rule.CompiledCondition.Matches(changes) {
			deny := u.ExpandGroups(rule.Deny)
			for _, key := range changes.SignatureKeys {
				for _, item := range deny {
					if item == "*" || key.Matches(pgp.Key(item)) {
						return fmt.Errorf("denied according to rule: %s", rule)
					}
				}
			}

			allow := u.ExpandGroups(rule.Allow)
			for _, key := range changes.SignatureKeys {
				for _, item := range allow {
					if item == "*" || key.Matches(pgp.Key(item)) {
						return nil
					}
				}
			}
		}
	}

	return fmt.Errorf("denied as no rule matches")
}
