package preview

import (
	"fmt"
	"strings"

	"github.com/docker/distribution/reference"
	"github.com/mitchellh/mapstructure"
	"github.com/porter-dev/switchboard/pkg/types"
	"k8s.io/apimachinery/pkg/util/validation"
)

func commonValidator(resource *types.Resource) (*Source, *Target, error) {
	source := &Source{}

	err := mapstructure.Decode(resource.Source, source)
	if err != nil {
		return nil, nil, fmt.Errorf("for resource '%s': error parsing source: %w", resource.Name, err)
	}

	target := &Target{}

	err = mapstructure.Decode(resource.Target, target)

	if err != nil {
		return nil, nil, fmt.Errorf("for resource '%s': error parsing target: %w", resource.Name, err)
	}

	return source, target, nil
}

func deployDriverValidator(resource *types.Resource) error {
	source, _, err := commonValidator(resource)
	if err != nil {
		return err
	}

	if source.Name == "" {
		return fmt.Errorf("for resource '%s': source name cannot be empty", resource.Name)
	}

	if source.Repo == "" {
		if source.Name == "web" || source.Name == "worker" || source.Name == "job" {
			source.Repo = "https://charts.getporter.dev"
		} else {
			source.Repo = "https://chart-addons.getporter.dev"
		}
	}

	if source.Repo == "https://charts.getporter.dev" {
		appConfig := &ApplicationConfig{}

		err = mapstructure.Decode(resource.Config, appConfig)

		if err != nil {
			return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
		}

		if appConfig.Build.Method == "" {
			return fmt.Errorf("for resource '%s': build method cannot be empty", resource.Name)
		} else if appConfig.Build.Method != "docker" &&
			appConfig.Build.Method != "pack" &&
			appConfig.Build.Method != "registry" {
			return fmt.Errorf("for resource '%s': build method must be one of 'docker', 'pack', or 'registry'", resource.Name)
		}

		if appConfig.Build.Method == "registry" {
			if appConfig.Build.Image == "" {
				return fmt.Errorf("for resource '%s': image cannot be empty when using the 'registry' build method",
					resource.Name)
			} else if !strings.Contains(appConfig.Build.Image, "{") {
				if len(strings.Split(appConfig.Build.Image, ":")) != 2 {
					return fmt.Errorf("for resource '%s': image must be in the format 'image:tag'", resource.Name)
				}

				// check for valid image
				_, err := reference.ParseNamed(appConfig.Build.Image)
				if err != nil {
					return fmt.Errorf("for resource '%s': error parsing image: %w", resource.Name, err)
				}
			}
		}

		for _, eg := range appConfig.EnvGroups {
			if errStrs := validation.IsDNS1123Label(eg.Name); len(errStrs) > 0 {
				str := fmt.Sprintf("for resource '%s': invalid characters found in env group '%s' name:",
					resource.Name, eg.Name)
				for _, errStr := range errStrs {
					str += fmt.Sprintf("\n  * %s", errStr)
				}

				return fmt.Errorf("%s", str)
			}
		}

		if len(appConfig.Values) > 0 {
			if source.Name == "web" {
				err := validateWebChartValues(appConfig.Values)
				if err != nil {
					return fmt.Errorf("for resource '%s': error validating values for web deployment: %w",
						resource.Name, err)
				}
			} else if source.Name == "worker" {
				err := validateWorkerChartValues(appConfig.Values)
				if err != nil {
					return fmt.Errorf("for resource '%s': error validating values for worker deployment: %w",
						resource.Name, err)
				}
			} else if source.Name == "job" {
				err := validateJobChartValues(appConfig.Values)
				if err != nil {
					return fmt.Errorf("for resource '%s': error validating values for job deployment: %w",
						resource.Name, err)
				}
			}
		}
	} else if source.Repo == "https://chart-addons.getporter.dev" {
		if len(resource.Config) > 0 {
			if source.Name == "postgresql" {
				err := validatePostgresChartValues(resource.Config)
				if err != nil {
					return fmt.Errorf("for resource '%s': error validating values for postgresql deployment: %w",
						resource.Name, err)
				}
			}
		}
	}

	return nil
}

func buildImageDriverValidator(resource *types.Resource) error {
	_, target, err := commonValidator(resource)
	if err != nil {
		return err
	}

	if target.AppName == "" {
		return fmt.Errorf("for resource '%s': target app_name is missing", resource.Name)
	} else {
		errStrs := validation.IsDNS1123Label(target.AppName)

		if len(errStrs) > 0 {
			str := fmt.Sprintf("for resource '%s': invalid characters found in app_name:", resource.Name)
			for _, errStr := range errStrs {
				str += fmt.Sprintf("\n  * %s", errStr)
			}

			return fmt.Errorf("%s", str)
		}
	}

	driverConfig := &BuildDriverConfig{}

	err = mapstructure.Decode(resource.Config, driverConfig)

	if err != nil {
		return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
	}

	if driverConfig.Build.Method == "" {
		return fmt.Errorf("for resource '%s': build method cannot be empty", resource.Name)
	} else if driverConfig.Build.Method != "docker" &&
		driverConfig.Build.Method != "pack" &&
		driverConfig.Build.Method != "registry" {
		return fmt.Errorf("for resource '%s': build method must be one of 'docker', 'pack', or 'registry'", resource.Name)
	}

	if driverConfig.Build.Method == "registry" {
		if driverConfig.Build.Image == "" {
			return fmt.Errorf("for resource '%s': image cannot be empty when using the 'registry' build method",
				resource.Name)
		} else if !strings.Contains(driverConfig.Build.Image, "{") {
			if len(strings.Split(driverConfig.Build.Image, ":")) != 2 {
				return fmt.Errorf("for resource '%s': image must be in the format 'image:tag'", resource.Name)
			}

			// check for valid image
			_, err := reference.ParseNamed(driverConfig.Build.Image)
			if err != nil {
				return fmt.Errorf("for resource '%s': error parsing image: %w", resource.Name, err)
			}
		}
	}

	for _, eg := range driverConfig.EnvGroups {
		if errStrs := validation.IsDNS1123Label(eg.Name); len(errStrs) > 0 {
			str := fmt.Sprintf("for resource '%s': invalid characters found in env group '%s' name:",
				resource.Name, eg.Name)
			for _, errStr := range errStrs {
				str += fmt.Sprintf("\n  * %s", errStr)
			}

			return fmt.Errorf("%s", str)
		}
	}

	return nil
}

func pushImageDriverValidator(resource *types.Resource) error {
	_, target, err := commonValidator(resource)
	if err != nil {
		return err
	}

	if target.AppName == "" {
		return fmt.Errorf("for resource '%s': target app_name is missing", resource.Name)
	} else {
		errStrs := validation.IsDNS1123Label(target.AppName)

		if len(errStrs) > 0 {
			str := fmt.Sprintf("for resource '%s': invalid characters found in app_name:", resource.Name)
			for _, errStr := range errStrs {
				str += fmt.Sprintf("\n  * %s", errStr)
			}

			return fmt.Errorf("%s", str)
		}
	}

	driverConfig := &PushDriverConfig{}

	err = mapstructure.Decode(resource.Config, driverConfig)

	if err != nil {
		return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
	}

	if driverConfig.Push.Image == "" {
		return fmt.Errorf("for resource '%s': image cannot be empty", resource.Name)
	} else if !strings.Contains(driverConfig.Push.Image, "{") {
		if len(strings.Split(driverConfig.Push.Image, ":")) != 2 {
			return fmt.Errorf("for resource '%s': image must be in the format 'image:tag'", resource.Name)
		}

		// check for valid image
		_, err := reference.ParseNamed(driverConfig.Push.Image)
		if err != nil {
			return fmt.Errorf("for resource '%s': error parsing image: %w", resource.Name, err)
		}
	}

	return nil
}

func updateConfigDriverValidator(resource *types.Resource) error {
	source, target, err := commonValidator(resource)
	if err != nil {
		return err
	}

	if target.AppName == "" {
		return fmt.Errorf("for resource '%s': target app_name is missing", resource.Name)
	} else {
		errStrs := validation.IsDNS1123Label(target.AppName)

		if len(errStrs) > 0 {
			str := fmt.Sprintf("for resource '%s': invalid characters found in app_name:", resource.Name)
			for _, errStr := range errStrs {
				str += fmt.Sprintf("\n  * %s", errStr)
			}

			return fmt.Errorf("%s", str)
		}
	}

	if source.Repo == "" {
		if source.Name == "web" || source.Name == "worker" || source.Name == "job" {
			source.Repo = "https://charts.getporter.dev"
		} else {
			source.Repo = "https://chart-addons.getporter.dev"
		}
	}

	driverConfig := &UpdateConfigDriverConfig{}

	err = mapstructure.Decode(resource.Config, driverConfig)

	if err != nil {
		return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
	}

	if driverConfig.UpdateConfig.Image == "" {
		return fmt.Errorf("for resource '%s': image cannot be empty", resource.Name)
	}

	for _, eg := range driverConfig.EnvGroups {
		if errStrs := validation.IsDNS1123Label(eg.Name); len(errStrs) > 0 {
			str := fmt.Sprintf("for resource '%s': invalid characters found in env group '%s' name:",
				resource.Name, eg.Name)
			for _, errStr := range errStrs {
				str += fmt.Sprintf("\n  * %s", errStr)
			}

			return fmt.Errorf("%s", str)
		}
	}

	if len(driverConfig.Values) > 0 && source.Repo == "https://charts.getporter.dev" {
		if source.Name == "web" {
			err := validateWebChartValues(driverConfig.Values)
			if err != nil {
				return fmt.Errorf("for resource '%s': error validating values for web deployment: %w",
					resource.Name, err)
			}
		} else if source.Name == "worker" {
			err := validateWorkerChartValues(driverConfig.Values)
			if err != nil {
				return fmt.Errorf("for resource '%s': error validating values for worker deployment: %w",
					resource.Name, err)
			}
		} else if source.Name == "job" {
			err := validateJobChartValues(driverConfig.Values)
			if err != nil {
				return fmt.Errorf("for resource '%s': error validating values for job deployment: %w",
					resource.Name, err)
			}
		}
	}

	return nil
}

func randomStringDriverValidator(resource *types.Resource) error {
	driverConfig := &RandomStringDriverConfig{}

	err := mapstructure.Decode(resource.Config, driverConfig)
	if err != nil {
		return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
	}

	return nil
}

func envGroupDriverValidator(resource *types.Resource) error {
	target := &Target{}

	err := mapstructure.Decode(resource.Target, target)
	if err != nil {
		return fmt.Errorf("for resource '%s': error parsing target: %w", resource.Name, err)
	}

	config := &EnvGroupDriverConfig{}

	err = mapstructure.Decode(resource.Config, config)

	if err != nil {
		return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
	}

	for _, eg := range config.EnvGroups {
		if errStrs := validation.IsDNS1123Label(eg.Name); len(errStrs) > 0 {
			str := fmt.Sprintf("for resource '%s': invalid characters found in env group '%s' name:",
				resource.Name, eg.Name)
			for _, errStr := range errStrs {
				str += fmt.Sprintf("\n  * %s", errStr)
			}

			return fmt.Errorf("%s", str)
		}
	}

	return nil
}

func osEnvDriverValidator(resource *types.Resource) error {
	return nil
}
