package exposition

import (
	certManager "antoine-roux.tk/projects/go/pulumi-library/crds/kubernetes/certmanager/v1"
	traefik "antoine-roux.tk/projects/go/pulumi-library/crds/kubernetes/traefik/v1alpha1"
	"fmt"
	"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
	meta "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
	networking "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/networking/v1"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type IngressConfiguration struct {
	Name            string
	Dns             string
	ResponseHeaders *traefik.MiddlewareSpecHeadersArgs
	services        []IngressServices
}

type IngressServices struct {
	Service *v1.Service
	Path    string
}

func NewIngressConfiguration(name string, dns string, allowAllOrigin bool, services []IngressServices) *IngressConfiguration {
	ingressConfiguration := &IngressConfiguration{
		Name:     name,
		Dns:      dns,
		services: services,
	}

	if allowAllOrigin {
		ingressConfiguration.ResponseHeaders = &traefik.MiddlewareSpecHeadersArgs{
			AccessControlAllowOriginList: toPulumiStringArray([]string{"*"}),
		}
	}
	return ingressConfiguration
}

func toPulumiStringArray(values []string) pulumi.StringArray {
	array := pulumi.StringArray{}
	for _, value := range values {
		array = append(array, pulumi.String(value))
	}
	return array
}

func (ingress *IngressConfiguration) CreateIngress(
	ctx *pulumi.Context,
	namespace *v1.Namespace,
	parentApplication pulumi.Resource,
	certificate *certManager.Certificate,
) error {

	var middlewares pulumi.StringInput
	if ingress.ResponseHeaders != nil {
		headerMiddleware, err := ingress.createMiddlewareAddResponseHeader(ctx, namespace, parentApplication)
		if err != nil {
			return err
		}

		middlewares = pulumi.All(namespace.Metadata.Name().Elem(), headerMiddleware.Metadata.Name().Elem()).ApplyT(func(args []interface{}) string {
			return fmt.Sprintf("kube-ingress-gzip-compress@kubernetescrd,%s-%s@kubernetescrd", args[0], args[1])
		}).(pulumi.StringOutput)
	} else {
		middlewares = pulumi.String("kube-ingress-gzip-compress@kubernetescrd")
	}

	ingressAnnotations := pulumi.StringMap{
		"traefik.ingress.kubernetes.io/router.middlewares": middlewares,
		"traefik.ingress.kubernetes.io/router.entrypoints": pulumi.String("websecure"),
	}

	// https routing
	var ingressPaths networking.HTTPIngressPathArray
	for _, service := range ingress.services {
		ingressPaths = append(ingressPaths, networking.HTTPIngressPathArgs{
			Path:     pulumi.String(service.Path),
			PathType: pulumi.String("Prefix"),
			Backend: &networking.IngressBackendArgs{
				Service: &networking.IngressServiceBackendArgs{
					Name: service.Service.Metadata.Name().Elem(),
					Port: &networking.ServiceBackendPortArgs{
						Name: pulumi.String("exposed-port"),
					},
				},
			},
		})
	}

	// create http redirect to https
	err := ingress.createHttpRedirectIngress(ctx, namespace, parentApplication, ingressPaths)
	if err != nil {
		return err
	}

	_, err = networking.NewIngress(ctx, fmt.Sprintf("%s-https", ingress.Name), &networking.IngressArgs{
		Metadata: &meta.ObjectMetaArgs{
			Namespace: namespace.Metadata.Name(),
			Labels: pulumi.StringMap{
				"app.kubernetes.io/part-of":    pulumi.String(ingress.Name),
				"app.kubernetes.io/managed-by": pulumi.String("pulumi"),
			},
			Annotations: ingressAnnotations,
		},
		Spec: &networking.IngressSpecArgs{
			IngressClassName: pulumi.String("traefik-internal"),
			Rules: &networking.IngressRuleArray{
				networking.IngressRuleArgs{
					Host: pulumi.StringPtr(ingress.Dns),
					Http: &networking.HTTPIngressRuleValueArgs{
						Paths: ingressPaths,
					},
				},
			},
			Tls: &networking.IngressTLSArray{
				networking.IngressTLSArgs{
					Hosts: pulumi.StringArray{
						pulumi.String(ingress.Dns),
					},
					SecretName: certificate.Spec.SecretName(),
				},
			},
		},
	}, pulumi.Parent(parentApplication))

	return err
}

func (ingress *IngressConfiguration) createHttpRedirectIngress(
	ctx *pulumi.Context,
	namespace *v1.Namespace,
	parentApplication pulumi.Resource,
	paths networking.HTTPIngressPathArray,
) error {

	ingressAnnotations := pulumi.StringMap{
		"traefik.ingress.kubernetes.io/router.middlewares": pulumi.String("kube-ingress-gzip-compress@kubernetescrd,kube-ingress-redirect-scheme-https@kubernetescrd"),
		"traefik.ingress.kubernetes.io/router.entrypoints": pulumi.String("web"),
	}

	_, err := networking.NewIngress(ctx, fmt.Sprintf("%s-http", ingress.Name), &networking.IngressArgs{
		Metadata: &meta.ObjectMetaArgs{
			Namespace: namespace.Metadata.Name(),
			Labels: pulumi.StringMap{
				"app.kubernetes.io/part-of":    pulumi.String(ingress.Name),
				"app.kubernetes.io/managed-by": pulumi.String("pulumi"),
			},
			Annotations: ingressAnnotations,
		},
		Spec: &networking.IngressSpecArgs{
			IngressClassName: pulumi.String("traefik-internal"),
			Rules: &networking.IngressRuleArray{
				networking.IngressRuleArgs{
					Host: pulumi.String(ingress.Dns),
					Http: &networking.HTTPIngressRuleValueArgs{
						Paths: paths,
					},
				},
			},
		},
	}, pulumi.Parent(parentApplication))
	if err != nil {
		return err
	}
	return nil
}

func (ingress *IngressConfiguration) createMiddlewareAddResponseHeader(
	ctx *pulumi.Context,
	namespace *v1.Namespace,
	parentApplication pulumi.Resource,
) (*traefik.Middleware, error) {

	middlewareName := fmt.Sprintf("%s-response-header-middleware", ingress.Name)
	return traefik.NewMiddleware(ctx, middlewareName, &traefik.MiddlewareArgs{
		Metadata: &meta.ObjectMetaArgs{
			Namespace: namespace.Metadata.Name(),
			Labels: pulumi.StringMap{
				"app.kubernetes.io/part-of":    pulumi.String(ingress.Name),
				"app.kubernetes.io/managed-by": pulumi.String("pulumi"),
			},
		},
		Spec: &traefik.MiddlewareSpecArgs{
			Headers: ingress.ResponseHeaders,
		},
	}, pulumi.Parent(parentApplication))
}