package utils

import (
	"context"

	"github.com/hashicorp/terraform-plugin-framework/attr"
	"github.com/hashicorp/terraform-plugin-framework/diag"
	"github.com/hashicorp/terraform-plugin-framework/path"
	"github.com/hashicorp/terraform-plugin-framework/types"
	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
)

// These helpers handle conversion from API types to terraform types for
// optional fields. The challenge with optional fields is that they may be
// either null or empty in terraform config, but the API returns nil for
// both.  In this case use the value from state to avoid "inconsistent result
// after apply" errors.
// https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md#planresourcechange

func ToOptionalSet(ctx context.Context, remote *[]string, priorState AttributeGetter, p path.Path) (types.Set, diag.Diagnostics) {
	if remote == nil || len(*remote) == 0 {
		setFromState := types.Set{}
		diags := priorState.GetAttribute(ctx, p, &setFromState)
		if diags.HasError() {
			return types.Set{}, diags
		}
		if setFromState.IsNull() || len(setFromState.Elements()) == 0 {
			return setFromState, diags
		}
	}

	if remote == nil {
		return types.SetNull(types.StringType), diag.Diagnostics{}
	}

	values := make([]attr.Value, len(*remote))
	for i, s := range *remote {
		values[i] = types.StringValue(s)
	}
	return types.SetValue(types.StringType, values)
}

func ToOptionalList(ctx context.Context, remote *[]string, priorState AttributeGetter, p path.Path) (types.List, diag.Diagnostics) {
	if remote == nil || len(*remote) == 0 {
		listFromState := types.List{}
		diags := priorState.GetAttribute(ctx, p, &listFromState)
		if diags.HasError() {
			return types.List{}, diags
		}
		if listFromState.IsNull() || len(listFromState.Elements()) == 0 {
			return listFromState, diags
		}
	}

	if remote == nil {
		return types.ListNull(types.StringType), diag.Diagnostics{}
	}

	values := make([]attr.Value, len(*remote))
	for i, s := range *remote {
		values[i] = types.StringValue(s)
	}
	return types.ListValue(types.StringType, values)
}

type Str interface {
	string |
		v20230301.EndpointKeyInfoFormat |
		v20230301.EndpointKeyInfoType
}

func ToOptionalString[S Str](ctx context.Context, remote *S, priorState AttributeGetter, p path.Path) (types.String, diag.Diagnostics) {
	if remote == nil || *remote == "" {
		stringFromState := types.String{}
		diags := priorState.GetAttribute(ctx, p, &stringFromState)
		if diags.HasError() {
			return types.String{}, diags
		}
		if stringFromState.IsNull() || stringFromState.ValueString() == "" {
			return stringFromState, diags
		}
	}

	if remote == nil {
		return types.StringNull(), diag.Diagnostics{}
	}

	return types.StringValue(string(*remote)), diag.Diagnostics{}
}

func ToOptionalBool(ctx context.Context, remote *bool, priorState AttributeGetter, p path.Path) (types.Bool, diag.Diagnostics) {
	if remote == nil || *remote == false {
		boolFromState := types.Bool{}
		diags := priorState.GetAttribute(ctx, p, &boolFromState)
		if diags.HasError() {
			return types.Bool{}, diags
		}
		if boolFromState.IsNull() || boolFromState.ValueBool() == false {
			return boolFromState, diags
		}
	}

	if remote == nil {
		return types.BoolNull(), diag.Diagnostics{}
	}

	return types.BoolValue(*remote), diag.Diagnostics{}
}

func ToOptionalInt(ctx context.Context, remote *int, priorState AttributeGetter, p path.Path) (types.Int64, diag.Diagnostics) {
	if remote == nil || *remote == 0 {
		intFromState := types.Int64{}
		diags := priorState.GetAttribute(ctx, p, &intFromState)
		if diags.HasError() {
			return types.Int64{}, diags
		}
		if intFromState.IsNull() || intFromState.ValueInt64() == 0 {
			return intFromState, diags
		}
	}

	if remote == nil {
		return types.Int64Null(), diag.Diagnostics{}
	}

	return types.Int64Value(int64(*remote)), diag.Diagnostics{}
}