package validate

import (
	"context"
	"fmt"
	"strings"
	"sync"

	"github.com/databricks/cli/bundle"
	"github.com/databricks/cli/libs/diag"
	"github.com/databricks/cli/libs/dyn"
	"github.com/databricks/cli/libs/fileset"
	"golang.org/x/sync/errgroup"
)

func ValidateSyncPatterns() bundle.ReadOnlyMutator {
	return &validateSyncPatterns{}
}

type validateSyncPatterns struct {
}

func (v *validateSyncPatterns) Name() string {
	return "validate:validate_sync_patterns"
}

func (v *validateSyncPatterns) Apply(ctx context.Context, rb bundle.ReadOnlyBundle) diag.Diagnostics {
	s := rb.Config().Sync
	if len(s.Exclude) == 0 && len(s.Include) == 0 {
		return nil
	}

	diags, err := checkPatterns(s.Exclude, "sync.exclude", rb)
	if err != nil {
		return diag.FromErr(err)
	}

	includeDiags, err := checkPatterns(s.Include, "sync.include", rb)
	if err != nil {
		return diag.FromErr(err)
	}

	return diags.Extend(includeDiags)
}

func checkPatterns(patterns []string, path string, rb bundle.ReadOnlyBundle) (diag.Diagnostics, error) {
	var mu sync.Mutex
	var errs errgroup.Group
	var diags diag.Diagnostics

	for i, pattern := range patterns {
		index := i
		fullPattern := pattern
		// If the pattern is negated, strip the negation prefix
		// and check if the pattern matches any files.
		// Negation in gitignore syntax means "don't look at this path'
		// So if p matches nothing it's useless negation, but if there are matches,
		// it means: do not include these files into result set
		p := strings.TrimPrefix(fullPattern, "!")
		errs.Go(func() error {
			fs, err := fileset.NewGlobSet(rb.BundleRoot(), []string{p})
			if err != nil {
				return err
			}

			all, err := fs.Files()
			if err != nil {
				return err
			}

			if len(all) == 0 {
				loc := location{path: fmt.Sprintf("%s[%d]", path, index), rb: rb}
				mu.Lock()
				diags = diags.Append(diag.Diagnostic{
					Severity:  diag.Warning,
					Summary:   fmt.Sprintf("Pattern %s does not match any files", fullPattern),
					Locations: []dyn.Location{loc.Location()},
					Paths:     []dyn.Path{loc.Path()},
				})
				mu.Unlock()
			}
			return nil
		})
	}

	return diags, errs.Wait()
}
