package syft

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/anchore/syft/syft/source"
)

// GetSource uses all of Syft's known source providers to attempt to resolve the user input to a usable source.Source
func GetSource(ctx context.Context, userInput string, cfg *GetSourceConfig) (source.Source, error) {
	if cfg == nil {
		cfg = DefaultGetSourceConfig()
	}

	providers, err := cfg.getProviders(userInput)
	if err != nil {
		return nil, err
	}

	var errs []error
	var fileNotFoundProviders []string

	// call each source provider until we find a valid source
	for _, p := range providers {
		src, err := p.Provide(ctx)
		if err != nil {
			if errors.Is(err, os.ErrNotExist) {
				fileNotFoundProviders = append(fileNotFoundProviders, p.Name())
			} else {
				errs = append(errs, fmt.Errorf("%s: %w", p.Name(), err))
			}
		}
		if err := validateSourcePlatform(src, cfg); err != nil {
			return nil, err
		}
		if src != nil {
			return src, nil
		}
	}

	if len(errs) == 0 {
		return nil, fmt.Errorf("no source providers were able to resolve the input %q", userInput)
	}

	if len(fileNotFoundProviders) > 0 {
		errs = append(errs, fmt.Errorf("additionally, the following providers failed with %w: %s", os.ErrNotExist, strings.Join(fileNotFoundProviders, ", ")))
	}

	return nil, sourceError(userInput, errs...)
}

func validateSourcePlatform(src source.Source, cfg *GetSourceConfig) error {
	if src == nil {
		return nil
	}
	if cfg == nil || cfg.SourceProviderConfig == nil || cfg.SourceProviderConfig.Platform == nil {
		return nil
	}

	meta := src.Describe().Metadata
	switch meta.(type) {
	case *source.ImageMetadata, source.ImageMetadata:
		return nil
	case *source.OCIModelMetadata, source.OCIModelMetadata:
		return nil
	case *source.SnapMetadata, source.SnapMetadata:
		return nil
	default:
		return fmt.Errorf("platform is not supported for this source type")
	}
}

func sourceError(userInput string, errs ...error) error {
	switch len(errs) {
	case 0:
		return nil
	case 1:
		return fmt.Errorf("an error occurred attempting to resolve '%s': %w", userInput, errs[0])
	}
	errorTexts := ""
	for _, e := range errs {
		errorTexts += fmt.Sprintf("\n  - %s", e)
	}
	return fmt.Errorf("errors occurred attempting to resolve '%s':%s", userInput, errorTexts)
}
