// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package azeventhubs

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal"
	"github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/eh"
	"github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp"
)

// EventHubProperties represents properties of the Event Hub, like the number of partitions.
type EventHubProperties struct {
	CreatedOn    time.Time
	Name         string
	PartitionIDs []string
}

// GetEventHubPropertiesOptions contains optional parameters for the GetEventHubProperties function
type GetEventHubPropertiesOptions struct {
	// For future expansion
}

// getEventHubProperties gets event hub properties, like the available partition IDs and when the Event Hub was created.
func getEventHubProperties(ctx context.Context, ns internal.NamespaceForManagementOps, rpcLink internal.RPCLink, eventHub string, options *GetEventHubPropertiesOptions) (EventHubProperties, error) {
	token, err := ns.GetTokenForEntity(eventHub)

	if err != nil {
		return EventHubProperties{}, internal.TransformError(err)
	}

	amqpMsg := &amqp.Message{
		ApplicationProperties: map[string]any{
			"operation":      "READ",
			"name":           eventHub,
			"type":           "com.microsoft:eventhub",
			"security_token": token.Token,
		},
	}

	resp, err := rpcLink.RPC(context.Background(), amqpMsg)

	if err != nil {
		return EventHubProperties{}, err
	}

	if resp.Code >= 300 {
		return EventHubProperties{}, fmt.Errorf("failed getting partition properties: %v", resp.Description)
	}

	return newEventHubProperties(resp.Message.Value)
}

// PartitionProperties are the properties for a single partition.
type PartitionProperties struct {
	// BeginningSequenceNumber is the first sequence number for a partition.
	BeginningSequenceNumber int64
	// EventHubName is the name of the Event Hub for this partition.
	EventHubName string

	// IsEmpty is true if the partition is empty, false otherwise.
	IsEmpty bool

	// LastEnqueuedOffset is the offset of latest enqueued event.
	LastEnqueuedOffset int64

	// LastEnqueuedOn is the date of latest enqueued event.
	LastEnqueuedOn time.Time

	// LastEnqueuedSequenceNumber is the sequence number of the latest enqueued event.
	LastEnqueuedSequenceNumber int64

	// PartitionID is the partition ID of this partition.
	PartitionID string
}

// GetPartitionPropertiesOptions are the options for the GetPartitionProperties function.
type GetPartitionPropertiesOptions struct {
	// For future expansion
}

// getPartitionProperties gets properties for a specific partition. This includes data like the last enqueued sequence number, the first sequence
// number and when an event was last enqueued to the partition.
func getPartitionProperties(ctx context.Context, ns internal.NamespaceForManagementOps, rpcLink internal.RPCLink, eventHub string, partitionID string, options *GetPartitionPropertiesOptions) (PartitionProperties, error) {
	token, err := ns.GetTokenForEntity(eventHub)

	if err != nil {
		return PartitionProperties{}, err
	}

	amqpMsg := &amqp.Message{
		ApplicationProperties: map[string]any{
			"operation":      "READ",
			"name":           eventHub,
			"type":           "com.microsoft:partition",
			"partition":      partitionID,
			"security_token": token.Token,
		},
	}

	resp, err := rpcLink.RPC(context.Background(), amqpMsg)

	if err != nil {
		return PartitionProperties{}, internal.TransformError(err)
	}

	if resp.Code >= 300 {
		return PartitionProperties{}, fmt.Errorf("failed getting partition properties: %v", resp.Description)
	}

	return newPartitionProperties(resp.Message.Value)
}

func newEventHubProperties(amqpValue any) (EventHubProperties, error) {
	m, ok := amqpValue.(map[string]any)

	if !ok {
		return EventHubProperties{}, nil
	}

	partitionIDs, ok := m["partition_ids"].([]string)

	if !ok {
		return EventHubProperties{}, fmt.Errorf("invalid message format")
	}

	name, ok := m["name"].(string)

	if !ok {
		return EventHubProperties{}, fmt.Errorf("invalid message format")
	}

	createdOn, ok := m["created_at"].(time.Time)

	if !ok {
		return EventHubProperties{}, fmt.Errorf("invalid message format")
	}

	return EventHubProperties{
		Name:         name,
		CreatedOn:    createdOn,
		PartitionIDs: partitionIDs,
	}, nil
}

func newPartitionProperties(amqpValue any) (PartitionProperties, error) {
	m, ok := amqpValue.(map[string]any)

	if !ok {
		return PartitionProperties{}, errors.New("invalid message format")
	}

	eventHubName, ok := m["name"].(string)

	if !ok {
		return PartitionProperties{}, errors.New("invalid message format")
	}

	partition, ok := m["partition"].(string)

	if !ok {
		return PartitionProperties{}, errors.New("invalid message format")
	}

	beginningSequenceNumber, ok := eh.ConvertToInt64(m["begin_sequence_number"])

	if !ok {
		return PartitionProperties{}, errors.New("invalid message format")
	}

	lastEnqueuedSequenceNumber, ok := eh.ConvertToInt64(m["last_enqueued_sequence_number"])

	if !ok {
		return PartitionProperties{}, errors.New("invalid message format")
	}

	lastEnqueuedOffsetStr, ok := m["last_enqueued_offset"].(string)

	if !ok {
		return PartitionProperties{}, errors.New("invalid message format")
	}

	lastEnqueuedOffset, err := strconv.ParseInt(lastEnqueuedOffsetStr, 10, 64)

	if err != nil {
		return PartitionProperties{}, fmt.Errorf("invalid message format: %w", err)
	}

	lastEnqueuedTime, ok := m["last_enqueued_time_utc"].(time.Time)

	if !ok {
		return PartitionProperties{}, errors.New("invalid message format")
	}

	isEmpty, ok := m["is_partition_empty"].(bool)

	if !ok {
		return PartitionProperties{}, errors.New("invalid message format")
	}

	return PartitionProperties{
		BeginningSequenceNumber:    beginningSequenceNumber,
		LastEnqueuedSequenceNumber: lastEnqueuedSequenceNumber,
		LastEnqueuedOffset:         lastEnqueuedOffset,
		LastEnqueuedOn:             lastEnqueuedTime,
		IsEmpty:                    isEmpty,
		PartitionID:                partition,
		EventHubName:               eventHubName,
	}, nil
}
