package aws

import (
	"cmp"
	"context"
	"fmt"
	"slices"
	"strconv"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/costexplorer"
	cetypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types"

	"github.com/tidbcloud/controlplane9/internal/verbose"
)

// CostExplorerAPI is the subset of Cost Explorer used for cost reporting.
type CostExplorerAPI interface {
	GetCostAndUsage(ctx context.Context, params *costexplorer.GetCostAndUsageInput,
		optFns ...func(*costexplorer.Options)) (*costexplorer.GetCostAndUsageOutput, error)
}

// CostResult holds the cost breakdown for a session.
type CostResult struct {
	Owner     string      `json:"owner,omitempty"`
	SessionID string      `json:"session_id"`
	Start     string      `json:"start"`
	End       string      `json:"end"`
	Entries   []CostEntry `json:"entries"`
	Total     float64     `json:"total"`
	Unit      string      `json:"unit"`
}

// FormatText implements model.TextFormatter.
func (r *CostResult) FormatText() string {
	var b strings.Builder
	fmt.Fprintf(&b, "cost: owner=%s session=%s period=%s..%s\n", r.Owner, r.SessionID, r.Start, r.End)
	for _, e := range r.Entries {
		fmt.Fprintf(&b, "  %-30s $%.4f %s\n", e.Service, e.Amount, e.Unit)
	}
	fmt.Fprintf(&b, "  %-30s $%.4f %s\n", "TOTAL", r.Total, r.Unit)
	return b.String()
}

// SessionCostReporter reports costs for cp9 sessions via Cost Explorer.
type SessionCostReporter struct {
	client CostExplorerAPI
}

// NewCostReporter creates a SessionCostReporter.
func NewCostReporter(client CostExplorerAPI) *SessionCostReporter {
	return &SessionCostReporter{client: client}
}

// SessionCost returns the cost breakdown for a tagged session over the given period.
func (r *SessionCostReporter) SessionCost(ctx context.Context, owner, sessionID string, start, end time.Time) ([]CostEntry, error) {
	// Cost Explorer DateInterval.End is exclusive and must be after Start.
	// Truncate to date boundaries and ensure at least one day.
	startDate := start.UTC().Truncate(24 * time.Hour)
	endDate := end.UTC().Truncate(24 * time.Hour)
	if !endDate.After(startDate) {
		endDate = startDate.Add(24 * time.Hour)
	}

	input := &costexplorer.GetCostAndUsageInput{
		TimePeriod: &cetypes.DateInterval{
			Start: aws.String(startDate.Format("2006-01-02")),
			End:   aws.String(endDate.Format("2006-01-02")),
		},
		Granularity: cetypes.GranularityDaily,
		Metrics:     []string{"UnblendedCost"},
		GroupBy: []cetypes.GroupDefinition{
			{Type: cetypes.GroupDefinitionTypeDimension, Key: aws.String("SERVICE")},
		},
		Filter: &cetypes.Expression{
			And: []cetypes.Expression{
				{
					Tags: &cetypes.TagValues{
						Key:    aws.String(TagOwner),
						Values: []string{owner},
					},
				},
				{
					Tags: &cetypes.TagValues{
						Key:    aws.String(TagSession),
						Values: []string{sessionID},
					},
				},
			},
		},
	}
	verbose.Logf("AWS costexplorer:GetCostAndUsage owner=%s session=%s start=%s end=%s",
		owner, sessionID, aws.ToString(input.TimePeriod.Start), aws.ToString(input.TimePeriod.End))
	out, err := r.client.GetCostAndUsage(ctx, input)
	if err != nil {
		return nil, fmt.Errorf("cost explorer GetCostAndUsage: %w", err)
	}
	verbose.Logf("AWS costexplorer:GetCostAndUsage owner=%s session=%s periods=%d", owner, sessionID, len(out.ResultsByTime))

	// Aggregate across time periods, grouped by service
	serviceCosts := make(map[string]float64)
	unit := "USD"
	for _, period := range out.ResultsByTime {
		for _, group := range period.Groups {
			svc := ""
			if len(group.Keys) > 0 {
				svc = group.Keys[0]
			}
			if m, ok := group.Metrics["UnblendedCost"]; ok {
				amount, parseErr := strconv.ParseFloat(aws.ToString(m.Amount), 64)
				if parseErr != nil {
					return nil, fmt.Errorf("parse cost amount %q for %s: %w", aws.ToString(m.Amount), svc, parseErr)
				}
				serviceCosts[svc] += amount
				if m.Unit != nil {
					unit = *m.Unit
				}
			}
		}
	}

	var entries []CostEntry
	for svc, amount := range serviceCosts {
		entries = append(entries, CostEntry{Service: svc, Amount: amount, Unit: unit})
	}
	slices.SortFunc(entries, func(a, b CostEntry) int { return cmp.Compare(a.Service, b.Service) })
	return entries, nil
}
