// Code generated by "gen/generator.go"; DO NOT EDIT.

package provider

import (
	"context"
	"fmt"
	"strings"
	"sync"

	"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
	"github.com/hashicorp/terraform-plugin-framework/path"
	"github.com/hashicorp/terraform-plugin-framework/resource"
	"github.com/hashicorp/terraform-plugin-framework/resource/schema"
	"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
	"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
	"github.com/hashicorp/terraform-plugin-framework/types"
	"github.com/hashicorp/terraform-plugin-log/tflog"
	"github.com/netascode/go-sdwan"
	"github.com/netascode/terraform-provider-sdwan/internal/provider/helpers"
)

// Ensure provider defined types fully satisfy framework interfaces
var _ resource.Resource = &RoutePolicyDefinitionResource{}
var _ resource.ResourceWithImportState = &RoutePolicyDefinitionResource{}

func NewRoutePolicyDefinitionResource() resource.Resource {
	return &RoutePolicyDefinitionResource{}
}

type RoutePolicyDefinitionResource struct {
	client      *sdwan.Client
	updateMutex *sync.Mutex
}

func (r *RoutePolicyDefinitionResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
	resp.TypeName = req.ProviderTypeName + "_route_policy_definition"
}

func (r *RoutePolicyDefinitionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = schema.Schema{
		// This description is used by the documentation generator and the language server.
		MarkdownDescription: helpers.NewAttributeDescription("This resource can manage a Route policy definition.").String,

		Attributes: map[string]schema.Attribute{
			"id": schema.StringAttribute{
				MarkdownDescription: "The id of the policy definition",
				Computed:            true,
				PlanModifiers: []planmodifier.String{
					stringplanmodifier.UseStateForUnknown(),
				},
			},
			"version": schema.Int64Attribute{
				MarkdownDescription: "The version of the policy definition",
				Computed:            true,
			},
			"type": schema.StringAttribute{
				MarkdownDescription: "The policy defintion type",
				Computed:            true,
				PlanModifiers: []planmodifier.String{
					stringplanmodifier.UseStateForUnknown(),
				},
			},
			"name": schema.StringAttribute{
				MarkdownDescription: "The name of the policy definition",
				Required:            true,
			},
			"description": schema.StringAttribute{
				MarkdownDescription: "The description of the policy definition",
				Required:            true,
			},
			"default_action": schema.StringAttribute{
				MarkdownDescription: helpers.NewAttributeDescription("Default action, either `accept` or `reject`").AddStringEnumDescription("accept", "reject").String,
				Optional:            true,
				Validators: []validator.String{
					stringvalidator.OneOf("accept", "reject"),
				},
			},
			"sequences": schema.ListNestedAttribute{
				MarkdownDescription: helpers.NewAttributeDescription("List of ACL sequences").String,
				Optional:            true,
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"id": schema.Int64Attribute{
							MarkdownDescription: helpers.NewAttributeDescription("Sequence ID").AddIntegerRangeDescription(1, 65534).String,
							Required:            true,
							Validators: []validator.Int64{
								int64validator.Between(1, 65534),
							},
						},
						"ip_type": schema.StringAttribute{
							MarkdownDescription: helpers.NewAttributeDescription("IP version, either `ipv4` or `ipv6`").AddStringEnumDescription("ipv4", "ipv6").String,
							Optional:            true,
							Validators: []validator.String{
								stringvalidator.OneOf("ipv4", "ipv6"),
							},
						},
						"name": schema.StringAttribute{
							MarkdownDescription: helpers.NewAttributeDescription("Sequence name").String,
							Optional:            true,
						},
						"base_action": schema.StringAttribute{
							MarkdownDescription: helpers.NewAttributeDescription("Base action, either `accept` or `reject`").AddStringEnumDescription("accept", "reject").String,
							Optional:            true,
							Validators: []validator.String{
								stringvalidator.OneOf("accept", "reject"),
							},
						},
						"match_entries": schema.ListNestedAttribute{
							MarkdownDescription: helpers.NewAttributeDescription("List of match entries").String,
							Optional:            true,
							NestedObject: schema.NestedAttributeObject{
								Attributes: map[string]schema.Attribute{
									"type": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Type of match entry").AddStringEnumDescription("address", "asPath", "advancedCommunity", "expandedCommunity", "extCommunity", "localPreference", "metric", "nextHop", "origin", "peer", "ompTag", "ospfTag").String,
										Required:            true,
										Validators: []validator.String{
											stringvalidator.OneOf("address", "asPath", "advancedCommunity", "expandedCommunity", "extCommunity", "localPreference", "metric", "nextHop", "origin", "peer", "ompTag", "ospfTag"),
										},
									},
									"prefix_list_id": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Prefix list ID").String,
										Optional:            true,
									},
									"prefix_list_version": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("Prefix list version").String,
										Optional:            true,
									},
									"as_path_list_id": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("AS path list ID").String,
										Optional:            true,
									},
									"as_path_list_version": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("AS path list version").String,
										Optional:            true,
									},
									"community_list_ids": schema.ListAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Community list IDs").String,
										ElementType:         types.StringType,
										Optional:            true,
									},
									"community_list_versions": schema.ListAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Community list versions").String,
										ElementType:         types.StringType,
										Optional:            true,
									},
									"community_list_match_flag": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Community list match flag").AddStringEnumDescription("and", "or", "exact").String,
										Optional:            true,
										Validators: []validator.String{
											stringvalidator.OneOf("and", "or", "exact"),
										},
									},
									"expanded_community_list_id": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Expanded community list ID").String,
										Optional:            true,
									},
									"expanded_community_list_version": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("Expanded community list version").String,
										Optional:            true,
									},
									"extended_community_list_id": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Extended community list ID").String,
										Optional:            true,
									},
									"extended_community_list_version": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("Extended community list version").String,
										Optional:            true,
									},
									"local_preference": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("Local preference").AddIntegerRangeDescription(0, 4294967295).String,
										Optional:            true,
										Validators: []validator.Int64{
											int64validator.Between(0, 4294967295),
										},
									},
									"metric": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("Metric").AddIntegerRangeDescription(0, 4294967295).String,
										Optional:            true,
										Validators: []validator.Int64{
											int64validator.Between(0, 4294967295),
										},
									},
									"next_hop": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Next hop IP").String,
										Optional:            true,
									},
									"origin": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Origin").AddStringEnumDescription("igp", "egp", "incomplete").String,
										Optional:            true,
										Validators: []validator.String{
											stringvalidator.OneOf("igp", "egp", "incomplete"),
										},
									},
									"peer": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Peer IP").String,
										Optional:            true,
									},
									"omp_tag": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("OMP tag").AddIntegerRangeDescription(0, 4294967295).String,
										Optional:            true,
										Validators: []validator.Int64{
											int64validator.Between(0, 4294967295),
										},
									},
									"ospf_tag": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("OSPF tag").AddIntegerRangeDescription(0, 4294967295).String,
										Optional:            true,
										Validators: []validator.Int64{
											int64validator.Between(0, 4294967295),
										},
									},
								},
							},
						},
						"action_entries": schema.ListNestedAttribute{
							MarkdownDescription: helpers.NewAttributeDescription("List of action entries").String,
							Optional:            true,
							NestedObject: schema.NestedAttributeObject{
								Attributes: map[string]schema.Attribute{
									"type": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Type of action entry").AddStringEnumDescription("aggregator", "asPath", "atomicAggregate", "community", "communityAdditive", "localPreference", "metric", "weight", "metrictype", "nextHop", "ompTag", "ospfTag", "origin", "originator").String,
										Required:            true,
										Validators: []validator.String{
											stringvalidator.OneOf("aggregator", "asPath", "atomicAggregate", "community", "communityAdditive", "localPreference", "metric", "weight", "metrictype", "nextHop", "ompTag", "ospfTag", "origin", "originator"),
										},
									},
									"aggregator": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("Aggregator").AddIntegerRangeDescription(0, 4294967295).String,
										Optional:            true,
										Validators: []validator.Int64{
											int64validator.Between(0, 4294967295),
										},
									},
									"aggregator_ip_address": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("IP address").String,
										Optional:            true,
									},
									"as_path_prepend": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Space separated list of ASN to prepend").String,
										Optional:            true,
									},
									"as_path_exclude": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Space separated list of ASN to exclude").String,
										Optional:            true,
									},
									"atomic_aggregate": schema.BoolAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Atomic aggregate").String,
										Optional:            true,
									},
									"community": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Community value, e.g. `1000:10000` or `internet` or `local-AS`").String,
										Optional:            true,
									},
									"community_additive": schema.BoolAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Community additive").String,
										Optional:            true,
									},
									"local_preference": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("Local preference").AddIntegerRangeDescription(0, 4294967295).String,
										Optional:            true,
										Validators: []validator.Int64{
											int64validator.Between(0, 4294967295),
										},
									},
									"metric": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("Metric").AddIntegerRangeDescription(0, 4294967295).String,
										Optional:            true,
										Validators: []validator.Int64{
											int64validator.Between(0, 4294967295),
										},
									},
									"weight": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("Weight").AddIntegerRangeDescription(0, 4294967295).String,
										Optional:            true,
										Validators: []validator.Int64{
											int64validator.Between(0, 4294967295),
										},
									},
									"metric_type": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Metric type").AddStringEnumDescription("type1", "type2").String,
										Optional:            true,
										Validators: []validator.String{
											stringvalidator.OneOf("type1", "type2"),
										},
									},
									"next_hop": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Next hop IP").String,
										Optional:            true,
									},
									"omp_tag": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("OMP tag").AddIntegerRangeDescription(0, 4294967295).String,
										Optional:            true,
										Validators: []validator.Int64{
											int64validator.Between(0, 4294967295),
										},
									},
									"ospf_tag": schema.Int64Attribute{
										MarkdownDescription: helpers.NewAttributeDescription("OSPF tag").AddIntegerRangeDescription(0, 4294967295).String,
										Optional:            true,
										Validators: []validator.Int64{
											int64validator.Between(0, 4294967295),
										},
									},
									"origin": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Origin").AddStringEnumDescription("igp", "egp", "incomplete").String,
										Optional:            true,
										Validators: []validator.String{
											stringvalidator.OneOf("igp", "egp", "incomplete"),
										},
									},
									"originator": schema.StringAttribute{
										MarkdownDescription: helpers.NewAttributeDescription("Originator IP").String,
										Optional:            true,
									},
								},
							},
						},
					},
				},
			},
		},
	}
}

func (r *RoutePolicyDefinitionResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
	if req.ProviderData == nil {
		return
	}

	r.client = req.ProviderData.(*SdwanProviderData).Client
	r.updateMutex = req.ProviderData.(*SdwanProviderData).UpdateMutex
}

func (r *RoutePolicyDefinitionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
	var plan Route

	// Read plan
	diags := req.Plan.Get(ctx, &plan)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}

	tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Name.ValueString()))

	// Create object
	body := plan.toBody(ctx)

	res, err := r.client.Post("/template/policy/definition/vedgeroute", body)
	if err != nil {
		resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST), got error: %s, %s", err, res.String()))
		return
	}

	plan.Id = types.StringValue(res.Get("definitionId").String())
	plan.Version = types.Int64Value(0)
	plan.Type = types.StringValue(plan.getType())

	tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Name.ValueString()))

	diags = resp.State.Set(ctx, &plan)
	resp.Diagnostics.Append(diags...)
}

func (r *RoutePolicyDefinitionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
	var state, oldState Route

	// Read state
	diags := req.State.Get(ctx, &state)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}
	diags = req.State.Get(ctx, &oldState)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}

	tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Name.String()))

	res, err := r.client.Get("/template/policy/definition/vedgeroute/" + state.Id.ValueString())
	if res.Get("error.message").String() == "Failed to find specified resource" {
		resp.State.RemoveResource(ctx)
		return
	} else if err != nil {
		resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String()))
		return
	}

	state.fromBody(ctx, res)
	state.updateVersions(ctx, oldState)

	tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Name.ValueString()))

	diags = resp.State.Set(ctx, &state)
	resp.Diagnostics.Append(diags...)
}

func (r *RoutePolicyDefinitionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
	var plan, state Route

	// Read plan
	diags := req.Plan.Get(ctx, &plan)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}
	// Read state
	diags = req.State.Get(ctx, &state)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}

	tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Name.ValueString()))

	if plan.hasChanges(ctx, &state) {
		body := plan.toBody(ctx)
		r.updateMutex.Lock()
		res, err := r.client.Put("/template/policy/definition/vedgeroute/"+plan.Id.ValueString(), body)
		r.updateMutex.Unlock()
		if err != nil {
			if strings.Contains(res.Get("error.message").String(), "Failed to acquire lock") {
				resp.Diagnostics.AddWarning("Client Warning", "Failed to modify policy due to policy being locked by another change. Policy changes will not be applied. Re-run 'terraform apply' to try again.")
			} else {
				resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String()))
				return
			}
		}
	} else {
		tflog.Debug(ctx, fmt.Sprintf("%s: No changes detected", plan.Name.ValueString()))
	}

	plan.Version = types.Int64Value(state.Version.ValueInt64() + 1)

	tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Name.ValueString()))

	diags = resp.State.Set(ctx, &plan)
	resp.Diagnostics.Append(diags...)
}

func (r *RoutePolicyDefinitionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
	var state Route

	// Read state
	diags := req.State.Get(ctx, &state)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}

	tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Name.ValueString()))

	res, err := r.client.Delete("/template/policy/definition/vedgeroute/" + state.Id.ValueString())
	if err != nil {
		resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (DELETE), got error: %s, %s", err, res.String()))
		return
	}

	tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Name.ValueString()))

	resp.State.RemoveResource(ctx)
}

func (r *RoutePolicyDefinitionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
	resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
