package application

import (
	v1 "antoine-roux.tk/projects/go/pulumi-library/crds/kubernetes/certmanager/v1"
	"antoine-roux.tk/projects/go/pulumi-library/pkg/exposition"
	"antoine-roux.tk/projects/go/pulumi-library/pkg/meta"
	"antoine-roux.tk/projects/go/pulumi-library/pkg/workload"
	"errors"
	"fmt"
	appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
	"slices"
)

type Configuration struct {
	Name           string
	Namespace      string
	Images         []ImagesConfiguration
	Dns            *string
	Replicas       *int
	Env            map[string]string
	AllowAllOrigin bool
}

type ImagesConfiguration struct {
	Image  string
	Path   string
	Health string
	Ready  string
}

type CreatedApplication struct {
	ApplicationName pulumi.StringOutput   `pulumi:"application"`
	DeploymentName  []pulumi.StringOutput `pulumi:"deployment"`
}

type application struct {
	pulumi.ResourceState

	Name           string
	Namespace      string
	Services       []service
	Dns            string
	Replicas       int
	Env            map[string]string
	AllowAllOrigin bool

	shouldCreateDns         bool
	shouldCreateCertificate bool
	shouldCreateIngress     bool
}

type service struct {
	Image  string
	Path   string
	Health string
	Ready  string
}

func NewApplication(ctx *pulumi.Context, configuration *Configuration) (*CreatedApplication, error) {

	if configuration.Name != "" && len(configuration.Images) > 0 {

		application := &application{
			Name:           configuration.Name,
			Namespace:      configuration.Namespace,
			Env:            configuration.Env,
			AllowAllOrigin: configuration.AllowAllOrigin,
		}

		var preventDuplicatePath []string
		for _, image := range configuration.Images {
			serviceConfiguration := service{
				Image:  image.Image,
				Path:   image.Path,
				Health: image.Health,
				Ready:  image.Ready,
			}
			if image.Path == "" {
				serviceConfiguration.Path = "/"
			}
			if image.Health == "" {
				serviceConfiguration.Health = "/"
			}
			if image.Ready == "" {
				serviceConfiguration.Ready = "/"
			}
			if slices.Contains(preventDuplicatePath, serviceConfiguration.Path) {
				return nil, errors.New("duplicate path in ingress applicationConfiguration")
			}
			application.Services = append(application.Services, serviceConfiguration)
			preventDuplicatePath = append(preventDuplicatePath, serviceConfiguration.Path)
		}

		if configuration.Replicas != nil {
			application.Replicas = *configuration.Replicas
		} else {
			application.Replicas = 1
		}

		if configuration.Dns != nil {
			application.Dns = *configuration.Dns
			application.shouldCreateDns = true
			application.shouldCreateCertificate = true
			application.shouldCreateIngress = true
		}

		err := ctx.RegisterComponentResource("pkg:application:CreatedApplication", configuration.Name, application)
		if err != nil {
			return nil, err
		}

		return application.createResources(ctx)
	} else {
		return nil, errors.New("missing required value ApplicationName or Image during generic application construction")
	}
}

func (application *application) createResources(ctx *pulumi.Context) (*CreatedApplication, error) {
	createdApplication := &CreatedApplication{
		ApplicationName: pulumi.String(application.Name).ToStringOutput(),
	}

	namespaceConfiguration := &meta.NamespaceConfiguration{
		Name:      application.Name,
		Namespace: application.Namespace,
	}
	namespace, err := namespaceConfiguration.CreateNamespace(ctx)
	if err != nil {
		return nil, err
	}

	var deployments []*appsv1.Deployment
	var ingressServices []exposition.IngressServices

	for index, service := range application.Services {
		indexedName := fmt.Sprintf("%s-%d", application.Name, index)
		appLabels := pulumi.StringMap{
			"app.kubernetes.io/name": pulumi.String(indexedName),
		}

		deploymentConfiguration := &workload.DeploymentConfiguration{
			Name:     indexedName,
			Env:      application.Env,
			Replicas: application.Replicas,
			ImageReference: &workload.ImageReference{
				Image:  service.Image,
				Health: service.Health,
				Ready:  service.Ready,
			},
		}
		deployment, err := deploymentConfiguration.CreateDeployment(ctx, namespace, application, appLabels)
		if err != nil {
			return nil, err
		}
		createdApplication.DeploymentName = append(createdApplication.DeploymentName, deployment.Metadata.Name().Elem())

		serviceConfiguration := exposition.ServiceConfiguration{
			Name: indexedName,
		}
		createdService, err := serviceConfiguration.CreateService(ctx, namespace, application, appLabels)
		if err != nil {
			return nil, err
		}

		ingressServices = append(ingressServices,
			exposition.IngressServices{
				Service: createdService,
				Path:    service.Path,
			})
		deployments = append(deployments, deployment)
	}

	if application.shouldCreateDns {
		dnsConfiguration := &exposition.DnsConfiguration{
			Name: application.Name,
			Dns:  application.Dns,
		}
		_, err = dnsConfiguration.CreateDNSRecord(ctx, namespace, application)
		if err != nil {
			return nil, err
		}

	}
	var certificate *v1.Certificate
	if application.shouldCreateCertificate {
		certificateConfiguration := &meta.CertificateConfiguration{
			Name: application.Name,
			Dns:  application.Dns,
		}
		certificate, err = certificateConfiguration.CreateCertificate(ctx, namespace, application)
		if err != nil {
			return nil, err
		}
	}
	if application.shouldCreateIngress {
		ingressConfiguration := exposition.NewIngressConfiguration(
			application.Name,
			application.Dns,
			application.AllowAllOrigin,
			ingressServices,
		)

		err = ingressConfiguration.CreateIngress(ctx, namespace, application, certificate)
		if err != nil {
			return nil, err
		}
	}

	outs := pulumi.Map{}
	for i, deployment := range deployments {
		outs[fmt.Sprintf("deployment-%d", i)] = deployment.Metadata.Name()
	}

	err = ctx.RegisterResourceOutputs(application, outs)
	if err != nil {
		return nil, err
	}

	return createdApplication, nil
}
