package azure

import (
	"context"
	"fmt"
	"strings"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
	"github.com/tidbcloud/observability/pkg/common/constants"
)

type PrivateEndpointClient struct {
	cli           *armnetwork.PrivateEndpointsClient
	nicCli        *armnetwork.InterfacesClient
	resourceGroup string
	subscription  string
}

func NewPrivateEndpointClient(subscription, resourceGroup string, cred azcore.TokenCredential) (*PrivateEndpointClient, error) {
	factory, err := armnetwork.NewClientFactory(subscription, cred, nil)
	if err != nil {
		return nil, err
	}
	return &PrivateEndpointClient{
		cli:           factory.NewPrivateEndpointsClient(),
		nicCli:        factory.NewInterfacesClient(),
		resourceGroup: resourceGroup,
		subscription:  subscription,
	}, nil
}

func (p *PrivateEndpointClient) Get(ctx context.Context, name string) (*armnetwork.PrivateEndpoint, error) {
	resp, err := p.cli.Get(ctx, p.resourceGroup, name, nil)
	if err != nil {
		return nil, err
	}

	return &resp.PrivateEndpoint, nil
}

func (p *PrivateEndpointClient) Create(ctx context.Context, name, vnetName, subnetID, serviceName, location string, tags map[string]string) (*armnetwork.PrivateEndpoint, error) {
	azTags := map[string]*string{}

	for k, v := range tags {
		// Tag names can't contain these characters: <, >, %, &, \, ?, /
		// https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources#limitations
		// TODO: normalize tag names with respect to the above restriction.
		azTags[strings.TrimPrefix(k, constants.Group+"/")] = to.Ptr(v)
	}
	poller, err := p.cli.BeginCreateOrUpdate(ctx, p.resourceGroup, name, armnetwork.PrivateEndpoint{
		Name:     to.Ptr(name),
		Tags:     azTags,
		Location: to.Ptr(location),
		Properties: &armnetwork.PrivateEndpointProperties{
			PrivateLinkServiceConnections: []*armnetwork.PrivateLinkServiceConnection{
				{
					Name: to.Ptr(fmt.Sprintf("conn-%s", name)),
					Properties: &armnetwork.PrivateLinkServiceConnectionProperties{
						PrivateLinkServiceID: to.Ptr(serviceName),
					},
				},
			},
			Subnet: &armnetwork.Subnet{ID: to.Ptr(subnetID)},
		},
	}, nil)
	if err != nil {
		return nil, err
	}
	resp, err := poller.PollUntilDone(ctx, nil)
	if err != nil {
		return nil, err
	}
	return &resp.PrivateEndpoint, nil
}

func (p *PrivateEndpointClient) Delete(ctx context.Context, name string) error {
	poller, err := p.cli.BeginDelete(ctx, p.resourceGroup, name, nil)
	if err != nil {
		return err
	}
	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

func (p *PrivateEndpointClient) GetPrivateIP(ctx context.Context, epName string) (*string, error) {
	ep, err := p.Get(ctx, epName)
	if err != nil {
		return nil, err
	}

	if ep.Properties == nil || len(ep.Properties.NetworkInterfaces) == 0 || ep.Properties.NetworkInterfaces[0].ID == nil {
		return nil, fmt.Errorf("no nic in pe %s", *ep.Name)
	}
	// get nic name from id
	// format of /subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkInterfaces/<nic-name>
	id := *ep.Properties.NetworkInterfaces[0].ID
	nicName := id[strings.LastIndex(id, "/")+1:]

	resp, err := p.nicCli.Get(ctx, p.resourceGroup, nicName, nil)
	if err != nil {
		return nil, err
	}
	if resp.Properties == nil || len(resp.Properties.IPConfigurations) == 0 || resp.Properties.IPConfigurations[0].Properties.PrivateIPAddress == nil {
		return nil, fmt.Errorf("no ip config in nic %s", nicName)
	}
	return resp.Properties.IPConfigurations[0].Properties.PrivateIPAddress, nil
}
