// Copyright © 2023 Cisco Systems, Inc. and its affiliates.
// All rights reserved.
//
// Licensed under the Mozilla Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	https://mozilla.org/MPL/2.0/
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: MPL-2.0
package provider

// DATA SOURCE NORMAL
import (
	"context"
	"log"

	merakigosdk "github.com/meraki/dashboard-api-go/v5/sdk"

	"github.com/hashicorp/terraform-plugin-framework/datasource"
	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

var (
	_ datasource.DataSource              = &OrganizationsSwitchPortsBySwitchDataSource{}
	_ datasource.DataSourceWithConfigure = &OrganizationsSwitchPortsBySwitchDataSource{}
)

func NewOrganizationsSwitchPortsBySwitchDataSource() datasource.DataSource {
	return &OrganizationsSwitchPortsBySwitchDataSource{}
}

type OrganizationsSwitchPortsBySwitchDataSource struct {
	client *merakigosdk.Client
}

func (d *OrganizationsSwitchPortsBySwitchDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
	if req.ProviderData == nil {
		return
	}
	client := req.ProviderData.(MerakiProviderData).Client
	d.client = client
}

// Metadata returns the data source type name.
func (d *OrganizationsSwitchPortsBySwitchDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
	resp.TypeName = req.ProviderTypeName + "_organizations_switch_ports_by_switch"
}

func (d *OrganizationsSwitchPortsBySwitchDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"configuration_updated_after": schema.StringAttribute{
				MarkdownDescription: `configurationUpdatedAfter query parameter. Optional parameter to filter items to switches where the configuration has been updated after the given timestamp.`,
				Optional:            true,
			},
			"ending_before": schema.StringAttribute{
				MarkdownDescription: `endingBefore query parameter. A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it.`,
				Optional:            true,
			},
			"mac": schema.StringAttribute{
				MarkdownDescription: `mac query parameter. Optional parameter to filter items to switches with MAC addresses that contain the search term or are an exact match.`,
				Optional:            true,
			},
			"macs": schema.ListAttribute{
				MarkdownDescription: `macs query parameter. Optional parameter to filter items to switches that have one of the provided MAC addresses.`,
				Optional:            true,
				ElementType:         types.StringType,
			},
			"name": schema.StringAttribute{
				MarkdownDescription: `name query parameter. Optional parameter to filter items to switches with names that contain the search term or are an exact match.`,
				Optional:            true,
			},
			"network_ids": schema.ListAttribute{
				MarkdownDescription: `networkIds query parameter. Optional parameter to filter items to switches in one of the provided networks.`,
				Optional:            true,
				ElementType:         types.StringType,
			},
			"organization_id": schema.StringAttribute{
				MarkdownDescription: `organizationId path parameter. Organization ID`,
				Required:            true,
			},
			"per_page": schema.Int64Attribute{
				MarkdownDescription: `perPage query parameter. The number of entries per page returned. Acceptable range is 3 50. Default is 50.`,
				Optional:            true,
			},
			"port_profile_ids": schema.ListAttribute{
				MarkdownDescription: `portProfileIds query parameter. Optional parameter to filter items to switches that contain switchports belonging to one of the specified port profiles.`,
				Optional:            true,
				ElementType:         types.StringType,
			},
			"serial": schema.StringAttribute{
				MarkdownDescription: `serial query parameter. Optional parameter to filter items to switches with serial number that contains the search term or are an exact match.`,
				Optional:            true,
			},
			"serials": schema.ListAttribute{
				MarkdownDescription: `serials query parameter. Optional parameter to filter items to switches that have one of the provided serials.`,
				Optional:            true,
				ElementType:         types.StringType,
			},
			"starting_after": schema.StringAttribute{
				MarkdownDescription: `startingAfter query parameter. A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it.`,
				Optional:            true,
			},
			"item": schema.SingleNestedAttribute{
				Computed: true,
				Attributes: map[string]schema.Attribute{

					"mac": schema.StringAttribute{
						MarkdownDescription: `The MAC address of the switch.`,
						Computed:            true,
					},
					"model": schema.StringAttribute{
						MarkdownDescription: `The model of the switch.`,
						Computed:            true,
					},
					"name": schema.StringAttribute{
						MarkdownDescription: `The name of the switch.`,
						Computed:            true,
					},
					"network": schema.SingleNestedAttribute{
						MarkdownDescription: `Identifying information of the switch's network.`,
						Computed:            true,
						Attributes: map[string]schema.Attribute{

							"id": schema.StringAttribute{
								MarkdownDescription: `The ID of the network.`,
								Computed:            true,
							},
							"name": schema.StringAttribute{
								MarkdownDescription: `The name of the network.`,
								Computed:            true,
							},
						},
					},
					"ports": schema.SetNestedAttribute{
						MarkdownDescription: `Ports belonging to the switch`,
						Computed:            true,
						NestedObject: schema.NestedAttributeObject{
							Attributes: map[string]schema.Attribute{

								"access_policy_type": schema.StringAttribute{
									MarkdownDescription: `The type of the access policy of the switch port. Only applicable to access ports. Can be one of 'Open', 'Custom access policy', 'MAC allow list' or 'Sticky MAC allow list'.`,
									Computed:            true,
								},
								"allowed_vlans": schema.StringAttribute{
									MarkdownDescription: `The VLANs allowed on the switch port. Only applicable to trunk ports.`,
									Computed:            true,
								},
								"enabled": schema.BoolAttribute{
									MarkdownDescription: `The status of the switch port.`,
									Computed:            true,
								},
								"link_negotiation": schema.StringAttribute{
									MarkdownDescription: `The link speed for the switch port.`,
									Computed:            true,
								},
								"name": schema.StringAttribute{
									MarkdownDescription: `The name of the switch port.`,
									Computed:            true,
								},
								"poe_enabled": schema.BoolAttribute{
									MarkdownDescription: `The PoE status of the switch port.`,
									Computed:            true,
								},
								"port_id": schema.StringAttribute{
									MarkdownDescription: `The identifier of the switch port.`,
									Computed:            true,
								},
								"rstp_enabled": schema.BoolAttribute{
									MarkdownDescription: `The rapid spanning tree protocol status.`,
									Computed:            true,
								},
								"sticky_mac_allow_list": schema.ListAttribute{
									MarkdownDescription: `The initial list of MAC addresses for sticky Mac allow list. Only applicable when 'accessPolicyType' is 'Sticky MAC allow list'.`,
									Computed:            true,
									ElementType:         types.StringType,
								},
								"sticky_mac_allow_list_limit": schema.Int64Attribute{
									MarkdownDescription: `The maximum number of MAC addresses for sticky MAC allow list. Only applicable when 'accessPolicyType' is 'Sticky MAC allow list'.`,
									Computed:            true,
								},
								"stp_guard": schema.StringAttribute{
									MarkdownDescription: `The state of the STP guard ('disabled', 'root guard', 'bpdu guard' or 'loop guard').`,
									Computed:            true,
								},
								"tags": schema.ListAttribute{
									MarkdownDescription: `The list of tags of the switch port.`,
									Computed:            true,
									ElementType:         types.StringType,
								},
								"type": schema.StringAttribute{
									MarkdownDescription: `The type of the switch port ('trunk', 'access', 'stack' or 'routed').`,
									Computed:            true,
								},
								"vlan": schema.Int64Attribute{
									MarkdownDescription: `The VLAN of the switch port. For a trunk port, this is the native VLAN. A null value will clear the value set for trunk ports.`,
									Computed:            true,
								},
								"voice_vlan": schema.Int64Attribute{
									MarkdownDescription: `The voice VLAN of the switch port. Only applicable to access ports.`,
									Computed:            true,
								},
							},
						},
					},
					"serial": schema.StringAttribute{
						MarkdownDescription: `The serial number of the switch.`,
						Computed:            true,
					},
				},
			},
		},
	}
}

func (d *OrganizationsSwitchPortsBySwitchDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
	var organizationsSwitchPortsBySwitch OrganizationsSwitchPortsBySwitch
	diags := req.Config.Get(ctx, &organizationsSwitchPortsBySwitch)
	if resp.Diagnostics.HasError() {
		return
	}

	selectedMethod := 1
	if selectedMethod == 1 {
		log.Printf("[DEBUG] Selected method: GetOrganizationSwitchPortsBySwitch")
		vvOrganizationID := organizationsSwitchPortsBySwitch.OrganizationID.ValueString()
		queryParams1 := merakigosdk.GetOrganizationSwitchPortsBySwitchQueryParams{}

		queryParams1.PerPage = int(organizationsSwitchPortsBySwitch.PerPage.ValueInt64())
		queryParams1.StartingAfter = organizationsSwitchPortsBySwitch.StartingAfter.ValueString()
		queryParams1.EndingBefore = organizationsSwitchPortsBySwitch.EndingBefore.ValueString()
		queryParams1.ConfigurationUpdatedAfter = organizationsSwitchPortsBySwitch.ConfigurationUpdatedAfter.ValueString()
		queryParams1.Mac = organizationsSwitchPortsBySwitch.Mac.ValueString()
		queryParams1.Macs = elementsToStrings(ctx, organizationsSwitchPortsBySwitch.Macs)
		queryParams1.Name = organizationsSwitchPortsBySwitch.Name.ValueString()
		queryParams1.NetworkIDs = elementsToStrings(ctx, organizationsSwitchPortsBySwitch.NetworkIDs)
		queryParams1.PortProfileIDs = elementsToStrings(ctx, organizationsSwitchPortsBySwitch.PortProfileIDs)
		queryParams1.Serial = organizationsSwitchPortsBySwitch.Serial.ValueString()
		queryParams1.Serials = elementsToStrings(ctx, organizationsSwitchPortsBySwitch.Serials)

		// has_unknown_response: None

		response1, restyResp1, err := d.client.Switch.GetOrganizationSwitchPortsBySwitch(vvOrganizationID, &queryParams1)

		if err != nil || response1 == nil {
			if restyResp1 != nil {
				log.Printf("[DEBUG] Retrieved error response %s", restyResp1.String())
			}
			resp.Diagnostics.AddError(
				"Failure when executing GetOrganizationSwitchPortsBySwitch",
				err.Error(),
			)
			return
		}

		organizationsSwitchPortsBySwitch = ResponseSwitchGetOrganizationSwitchPortsBySwitchItemToBody(organizationsSwitchPortsBySwitch, response1)
		diags = resp.State.Set(ctx, &organizationsSwitchPortsBySwitch)
		resp.Diagnostics.Append(diags...)
		if resp.Diagnostics.HasError() {
			return
		}

	}
}

// structs
type OrganizationsSwitchPortsBySwitch struct {
	OrganizationID            types.String                                      `tfsdk:"organization_id"`
	PerPage                   types.Int64                                       `tfsdk:"per_page"`
	StartingAfter             types.String                                      `tfsdk:"starting_after"`
	EndingBefore              types.String                                      `tfsdk:"ending_before"`
	ConfigurationUpdatedAfter types.String                                      `tfsdk:"configuration_updated_after"`
	Mac                       types.String                                      `tfsdk:"mac"`
	Macs                      types.List                                        `tfsdk:"macs"`
	Name                      types.String                                      `tfsdk:"name"`
	NetworkIDs                types.List                                        `tfsdk:"network_ids"`
	PortProfileIDs            types.List                                        `tfsdk:"port_profile_ids"`
	Serial                    types.String                                      `tfsdk:"serial"`
	Serials                   types.List                                        `tfsdk:"serials"`
	Item                      *ResponseSwitchGetOrganizationSwitchPortsBySwitch `tfsdk:"item"`
}

type ResponseSwitchGetOrganizationSwitchPortsBySwitch struct {
	Mac     types.String                                             `tfsdk:"mac"`
	Model   types.String                                             `tfsdk:"model"`
	Name    types.String                                             `tfsdk:"name"`
	Network *ResponseSwitchGetOrganizationSwitchPortsBySwitchNetwork `tfsdk:"network"`
	Ports   *[]ResponseSwitchGetOrganizationSwitchPortsBySwitchPorts `tfsdk:"ports"`
	Serial  types.String                                             `tfsdk:"serial"`
}

type ResponseSwitchGetOrganizationSwitchPortsBySwitchNetwork struct {
	ID   types.String `tfsdk:"id"`
	Name types.String `tfsdk:"name"`
}

type ResponseSwitchGetOrganizationSwitchPortsBySwitchPorts struct {
	AccessPolicyType        types.String `tfsdk:"access_policy_type"`
	AllowedVLANs            types.String `tfsdk:"allowed_vlans"`
	Enabled                 types.Bool   `tfsdk:"enabled"`
	LinkNegotiation         types.String `tfsdk:"link_negotiation"`
	Name                    types.String `tfsdk:"name"`
	PoeEnabled              types.Bool   `tfsdk:"poe_enabled"`
	PortID                  types.String `tfsdk:"port_id"`
	RstpEnabled             types.Bool   `tfsdk:"rstp_enabled"`
	StickyMacAllowList      types.List   `tfsdk:"sticky_mac_allow_list"`
	StickyMacAllowListLimit types.Int64  `tfsdk:"sticky_mac_allow_list_limit"`
	StpGuard                types.String `tfsdk:"stp_guard"`
	Tags                    types.List   `tfsdk:"tags"`
	Type                    types.String `tfsdk:"type"`
	VLAN                    types.Int64  `tfsdk:"vlan"`
	VoiceVLAN               types.Int64  `tfsdk:"voice_vlan"`
}

// ToBody
func ResponseSwitchGetOrganizationSwitchPortsBySwitchItemToBody(state OrganizationsSwitchPortsBySwitch, response *merakigosdk.ResponseSwitchGetOrganizationSwitchPortsBySwitch) OrganizationsSwitchPortsBySwitch {
	itemState := ResponseSwitchGetOrganizationSwitchPortsBySwitch{
		Mac: func() types.String {
			if response.Mac != "" {
				return types.StringValue(response.Mac)
			}
			return types.String{}
		}(),
		Model: func() types.String {
			if response.Model != "" {
				return types.StringValue(response.Model)
			}
			return types.String{}
		}(),
		Name: func() types.String {
			if response.Name != "" {
				return types.StringValue(response.Name)
			}
			return types.String{}
		}(),
		Network: func() *ResponseSwitchGetOrganizationSwitchPortsBySwitchNetwork {
			if response.Network != nil {
				return &ResponseSwitchGetOrganizationSwitchPortsBySwitchNetwork{
					ID: func() types.String {
						if response.Network.ID != "" {
							return types.StringValue(response.Network.ID)
						}
						return types.String{}
					}(),
					Name: func() types.String {
						if response.Network.Name != "" {
							return types.StringValue(response.Network.Name)
						}
						return types.String{}
					}(),
				}
			}
			return nil
		}(),
		Ports: func() *[]ResponseSwitchGetOrganizationSwitchPortsBySwitchPorts {
			if response.Ports != nil {
				result := make([]ResponseSwitchGetOrganizationSwitchPortsBySwitchPorts, len(*response.Ports))
				for i, ports := range *response.Ports {
					result[i] = ResponseSwitchGetOrganizationSwitchPortsBySwitchPorts{
						AccessPolicyType: func() types.String {
							if ports.AccessPolicyType != "" {
								return types.StringValue(ports.AccessPolicyType)
							}
							return types.String{}
						}(),
						AllowedVLANs: func() types.String {
							if ports.AllowedVLANs != "" {
								return types.StringValue(ports.AllowedVLANs)
							}
							return types.String{}
						}(),
						Enabled: func() types.Bool {
							if ports.Enabled != nil {
								return types.BoolValue(*ports.Enabled)
							}
							return types.Bool{}
						}(),
						LinkNegotiation: func() types.String {
							if ports.LinkNegotiation != "" {
								return types.StringValue(ports.LinkNegotiation)
							}
							return types.String{}
						}(),
						Name: func() types.String {
							if ports.Name != "" {
								return types.StringValue(ports.Name)
							}
							return types.String{}
						}(),
						PoeEnabled: func() types.Bool {
							if ports.PoeEnabled != nil {
								return types.BoolValue(*ports.PoeEnabled)
							}
							return types.Bool{}
						}(),
						PortID: func() types.String {
							if ports.PortID != "" {
								return types.StringValue(ports.PortID)
							}
							return types.String{}
						}(),
						RstpEnabled: func() types.Bool {
							if ports.RstpEnabled != nil {
								return types.BoolValue(*ports.RstpEnabled)
							}
							return types.Bool{}
						}(),
						StickyMacAllowList: StringSliceToList(ports.StickyMacAllowList),
						StickyMacAllowListLimit: func() types.Int64 {
							if ports.StickyMacAllowListLimit != nil {
								return types.Int64Value(int64(*ports.StickyMacAllowListLimit))
							}
							return types.Int64{}
						}(),
						StpGuard: func() types.String {
							if ports.StpGuard != "" {
								return types.StringValue(ports.StpGuard)
							}
							return types.String{}
						}(),
						Tags: StringSliceToList(ports.Tags),
						Type: func() types.String {
							if ports.Type != "" {
								return types.StringValue(ports.Type)
							}
							return types.String{}
						}(),
						VLAN: func() types.Int64 {
							if ports.VLAN != nil {
								return types.Int64Value(int64(*ports.VLAN))
							}
							return types.Int64{}
						}(),
						VoiceVLAN: func() types.Int64 {
							if ports.VoiceVLAN != nil {
								return types.Int64Value(int64(*ports.VoiceVLAN))
							}
							return types.Int64{}
						}(),
					}
				}
				return &result
			}
			return nil
		}(),
		Serial: func() types.String {
			if response.Serial != "" {
				return types.StringValue(response.Serial)
			}
			return types.String{}
		}(),
	}
	state.Item = &itemState
	return state
}
