package aks

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v8"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/azure"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/privatelinkservice/task"
	"k8s.io/utils/ptr"
)

const (
	privateLinkServiceConnectionApproved = "Approved"
	privateLinkServiceConnectionRejected = "Rejected"
)

type Endpoint struct {
	*task.Context
	azureCli azure.Client
}

func NewEndpoint(c *task.Context, azureCli azure.Client) task.VpcEndpointProvider {
	return &Endpoint{Context: c, azureCli: azureCli}
}

func (e *Endpoint) GetAllVPCEndpointConns(ctx context.Context) ([]task.VpcEndpointConnection, error) {
	conns, err := e.listConnections(ctx)
	if err != nil {
		return nil, err
	}

	out := make([]task.VpcEndpointConnection, 0, len(conns))
	for _, conn := range conns {
		out = append(out, *convertAzureConnection(conn))
	}
	return out, nil
}

func (e *Endpoint) AcceptVPCEndpoints(ctx context.Context, acceptList []task.VpcEndpointConnection) error {
	return e.updateConnections(ctx, acceptList, privateLinkServiceConnectionApproved)
}

func (e *Endpoint) RejectVPCEndpoints(ctx context.Context, rejectList []task.VpcEndpointConnection) error {
	return e.updateConnections(ctx, rejectList, privateLinkServiceConnectionRejected)
}

func (e *Endpoint) ShouldForceDeleteVPCEndpoints() bool {
	return e.PrivateLinkService.Status.AzurePrivateLinkService.Name != ""
}

func (e *Endpoint) DeleteVPCEndpoints(ctx context.Context, deleteList []task.VpcEndpointConnection) error {
	subscriptionID, resourceGroup, serviceName, err := e.getAzureClientMeta()
	if err != nil {
		return err
	}
	plsClient, err := e.azureCli.PrivateLinkServices(subscriptionID)
	if err != nil {
		return err
	}

	var errs []error
	for _, conn := range deleteList {
		if err := plsClient.DeletePrivateEndpointConnection(ctx, resourceGroup, serviceName, conn.ConnectionID); err != nil {
			errs = append(errs, fmt.Errorf("delete private endpoint connection %s: %w", conn.ConnectionID, err))
		}
	}
	return errors.Join(errs...)
}

func (e *Endpoint) updateConnections(ctx context.Context, connections []task.VpcEndpointConnection, status string) error {
	subscriptionID, resourceGroup, serviceName, err := e.getAzureClientMeta()
	if err != nil {
		return err
	}
	plsClient, err := e.azureCli.PrivateLinkServices(subscriptionID)
	if err != nil {
		return err
	}

	for _, conn := range connections {
		if err := updateConnectionState(ctx, plsClient, resourceGroup, serviceName, conn.ConnectionID, status); err != nil {
			return err
		}
	}
	return nil
}

func (e *Endpoint) listConnections(ctx context.Context) ([]*armnetwork.PrivateEndpointConnection, error) {
	subscriptionID, resourceGroup, serviceName, err := e.getAzureClientMeta()
	if err != nil {
		return nil, err
	}
	plsClient, err := e.azureCli.PrivateLinkServices(subscriptionID)
	if err != nil {
		return nil, err
	}
	results, err := plsClient.ListPrivateEndpointConnections(ctx, resourceGroup, serviceName)
	if err != nil {
		if isNotFound(err) {
			return nil, nil
		}
		return nil, err
	}
	return results, nil
}

func (e *Endpoint) getAzureClientMeta() (string, string, string, error) {
	if e.azureCli == nil {
		return "", "", "", fmt.Errorf("azure client is not configured")
	}
	subscriptionID := e.ProviderConfigName()
	if subscriptionID == "" {
		return "", "", "", fmt.Errorf("subscription id is empty")
	}
	resourceGroup := e.VPC.Status.ResourceGroup.ID
	if resourceGroup == "" {
		return "", "", "", fmt.Errorf("resource group is empty")
	}
	serviceName := e.PrivateLinkService.Status.AzurePrivateLinkService.Name
	if serviceName == "" {
		return "", "", "", fmt.Errorf("azure private link service name is empty")
	}
	return subscriptionID, resourceGroup, serviceName, nil
}

func isNotFound(err error) bool {
	var respErr *azcore.ResponseError
	if errors.As(err, &respErr) {
		return respErr.StatusCode == http.StatusNotFound
	}
	return false
}

func updateConnectionState(
	ctx context.Context,
	cli azure.PrivateLinkServicesAPI,
	resourceGroup string,
	serviceName string,
	connectionName string,
	status string,
) error {
	params := armnetwork.PrivateEndpointConnection{
		Properties: &armnetwork.PrivateEndpointConnectionProperties{
			PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
				Status:      ptr.To(status),
				Description: ptr.To(fmt.Sprintf("%s by TiDB Cloud", status)),
			},
		},
	}

	return cli.UpdatePrivateEndpointConnection(ctx, resourceGroup, serviceName, connectionName, params)
}

func convertAzureConnection(conn *armnetwork.PrivateEndpointConnection) *task.VpcEndpointConnection {
	out := &task.VpcEndpointConnection{
		EndpointID:   ptr.Deref(conn.Properties.PrivateEndpoint.ID, ""),
		ConnectionID: ptr.Deref(conn.Name, ""),
		Message:      ptr.Deref(conn.Properties.PrivateLinkServiceConnectionState.Description, ""),
	}
	state := strings.ToLower(ptr.Deref(conn.Properties.PrivateLinkServiceConnectionState.Status, ""))
	switch state {
	case strings.ToLower(privateLinkServiceConnectionApproved):
		out.Phase = v1alpha1.ConnectionPhaseActive
	case strings.ToLower(privateLinkServiceConnectionRejected):
		out.Phase = v1alpha1.ConnectionPhaseFailed
	case "pending":
		out.Phase = v1alpha1.ConnectionPhasePending
	case "disconnected":
		out.Phase = v1alpha1.ConnectionPhaseDeleting
	default:
		out.Phase = v1alpha1.ConnectionPhaseFailed
		if out.Message == "" {
			out.Message = fmt.Sprintf("private endpoint connection is in unknown state %s", state)
		}
	}
	return out
}
