package awsauth

import (
	"context"
	"fmt"

	"gopkg.in/yaml.v2"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	AWSAuthNamespace = "kube-system"
	AWSAuthName      = "aws-auth"
)

type AwsAuthReaderWriter interface {
	AwsAuthReader
	AwsAuthWriter
}

type AwsAuthReader interface {
	Get(ctx context.Context) (*AwsAuthData, error)
}

type AwsAuthWriter interface {
	Update(ctx context.Context, awsAuthInfo *AwsAuthData) error
}

func NewAwsAuthReaderWriter(cli client.Client) AwsAuthReaderWriter {
	return &awsAuthReaderWriter{
		awsAuthReader: awsAuthReader{cli},
		awsAuthWriter: awsAuthWriter{cli},
	}
}

func NewAwsAuthReader(cli client.Client) AwsAuthReader {
	return &awsAuthReader{
		readCli: cli,
	}
}

func NewAwsAuthWriter(cli client.Client) AwsAuthWriter {
	return &awsAuthWriter{
		cli: cli,
	}
}

type awsAuthReaderWriter struct {
	awsAuthReader
	awsAuthWriter
}

type awsAuthWriter struct {
	cli client.Client
}

func (w *awsAuthWriter) Update(ctx context.Context, awsAuthInfo *AwsAuthData) error {
	cm := &v1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      AWSAuthName,
			Namespace: AWSAuthNamespace,
		},
	}
	if err := w.cli.Get(ctx, client.ObjectKeyFromObject(cm), cm); err != nil {
		return err
	}

	cur, err := parseAuthData(cm)
	if err != nil {
		return fmt.Errorf("parse aws-auth configmap: %v", err)
	}
	cur.MergeAuthData(awsAuthInfo)
	mapRoles, err := yaml.Marshal(cur.MapRoles)
	if err != nil {
		return err
	}
	mapUsers, err := yaml.Marshal(cur.MapUsers)
	if err != nil {
		return err
	}

	cm.Data = map[string]string{
		"mapRoles": string(mapRoles),
		"mapUsers": string(mapUsers),
	}

	return w.cli.Update(ctx, cm)
}

type awsAuthReader struct {
	readCli client.Reader
}

func (r *awsAuthReader) Get(ctx context.Context) (*AwsAuthData, error) {
	cm := new(v1.ConfigMap)
	err := r.readCli.Get(ctx, client.ObjectKey{
		Name:      AWSAuthName,
		Namespace: AWSAuthNamespace,
	}, cm)
	if err != nil {
		return nil, err
	}
	return parseAuthData(cm)
}

func parseAuthData(cm *v1.ConfigMap) (*AwsAuthData, error) {
	authData := new(AwsAuthData)
	err := yaml.Unmarshal([]byte(cm.Data["mapRoles"]), &authData.MapRoles)
	if err != nil {
		return authData, err
	}

	err = yaml.Unmarshal([]byte(cm.Data["mapUsers"]), &authData.MapUsers)
	if err != nil {
		return authData, err
	}
	return authData, nil
}
