package oci

import (
	"context"
	"fmt"
	"time"

	"github.com/oracle/oci-go-sdk/v65/common"
	"github.com/oracle/oci-go-sdk/v65/monitoring"
)

// trafficNamespace 是 VNIC 流量指标所在的 Monitoring 命名空间；
// 指标由 OCI 自动上报，原始点 1 分钟一个，这里按天聚合。
const (
	trafficNamespace  = "oci_vcn"
	trafficResolution = "1440m"
	metricVnicInbound = "VnicFromNetworkBytes" // 实例收到的字节
	metricVnicOutbund = "VnicToNetworkBytes"   // 实例发出的字节
)

// TrafficQuery 是一次实例流量统计的条件。
type TrafficQuery struct {
	Region     string
	InstanceID string
	StartTime  time.Time
	EndTime    time.Time
}

// TrafficPoint 是一个聚合窗口（天）的流量字节数。
type TrafficPoint struct {
	Timestamp time.Time `json:"timestamp"`
	Bytes     float64   `json:"bytes"`
}

// VnicTraffic 是单块 VNIC 的进出流量。
type VnicTraffic struct {
	VnicID        string         `json:"vnicId"`
	InboundBytes  float64        `json:"inboundBytes"`
	OutboundBytes float64        `json:"outboundBytes"`
	Inbound       []TrafficPoint `json:"inbound"`
	Outbound      []TrafficPoint `json:"outbound"`
}

// InstanceTraffic 是实例全部 VNIC 的流量汇总。
type InstanceTraffic struct {
	InstanceID    string        `json:"instanceId"`
	InboundBytes  float64       `json:"inboundBytes"`
	OutboundBytes float64       `json:"outboundBytes"`
	Vnics         []VnicTraffic `json:"vnics"`
}

func (c *RealClient) monitoringClient(cred Credentials, region string) (monitoring.MonitoringClient, error) {
	mc, err := monitoring.NewMonitoringClientWithConfigurationProvider(provider(cred))
	if err != nil {
		return mc, fmt.Errorf("new monitoring client: %w", err)
	}
	applyProxy(&mc.BaseClient, cred)
	if region != "" {
		mc.SetRegion(normalizeRegion(region))
	}
	return mc, nil
}

// SummarizeInstanceTraffic 实现 Client：逐 VNIC 查询进出流量并汇总。
func (c *RealClient) SummarizeInstanceTraffic(ctx context.Context, cred Credentials, q TrafficQuery) (InstanceTraffic, error) {
	mc, err := c.monitoringClient(cred, q.Region)
	if err != nil {
		return InstanceTraffic{}, err
	}
	// VNIC 指标上报在实例所在 compartment，查询须用同一 compartment 才有数据
	vnics, compartmentID, err := c.instanceVnics(ctx, cred, q.Region, q.InstanceID)
	if err != nil {
		return InstanceTraffic{}, err
	}
	result := InstanceTraffic{InstanceID: q.InstanceID, Vnics: make([]VnicTraffic, 0, len(vnics))}
	for _, vnic := range vnics {
		vt := VnicTraffic{VnicID: deref(vnic.Id)}
		if vt.Inbound, err = summarizeVnicMetric(ctx, mc, compartmentID, vt.VnicID, metricVnicInbound, q); err != nil {
			return InstanceTraffic{}, err
		}
		if vt.Outbound, err = summarizeVnicMetric(ctx, mc, compartmentID, vt.VnicID, metricVnicOutbund, q); err != nil {
			return InstanceTraffic{}, err
		}
		vt.Inbound, vt.Outbound = orEmpty(vt.Inbound), orEmpty(vt.Outbound)
		vt.InboundBytes, vt.OutboundBytes = sumTraffic(vt.Inbound), sumTraffic(vt.Outbound)
		result.InboundBytes += vt.InboundBytes
		result.OutboundBytes += vt.OutboundBytes
		result.Vnics = append(result.Vnics, vt)
	}
	return result, nil
}

// summarizeVnicMetric 按天聚合查询单块 VNIC 的一个流量指标；
// compartmentID 须是实例所在 compartment，指标数据挂在该 compartment 下。
func summarizeVnicMetric(ctx context.Context, mc monitoring.MonitoringClient, compartmentID, vnicID, metric string, q TrafficQuery) ([]TrafficPoint, error) {
	query := fmt.Sprintf("%s[%s]{resourceId = %q}.sum()", metric, trafficResolution, vnicID)
	resolution := trafficResolution
	resp, err := mc.SummarizeMetricsData(ctx, monitoring.SummarizeMetricsDataRequest{
		CompartmentId: &compartmentID,
		SummarizeMetricsDataDetails: monitoring.SummarizeMetricsDataDetails{
			Namespace:  common.String(trafficNamespace),
			Query:      &query,
			StartTime:  &common.SDKTime{Time: q.StartTime},
			EndTime:    &common.SDKTime{Time: q.EndTime},
			Resolution: &resolution,
		},
	})
	if err != nil {
		return nil, fmt.Errorf("summarize %s of vnic %s: %w", metric, vnicID, err)
	}
	var points []TrafficPoint
	for _, item := range resp.Items {
		for _, dp := range item.AggregatedDatapoints {
			if dp.Timestamp == nil || dp.Value == nil {
				continue
			}
			points = append(points, TrafficPoint{Timestamp: dp.Timestamp.Time, Bytes: *dp.Value})
		}
	}
	return orEmpty(points), nil
}

func sumTraffic(points []TrafficPoint) float64 {
	var total float64
	for _, p := range points {
		total += p.Bytes
	}
	return total
}
