package config

import (
	"fmt"
	"strconv"
	"time"
)

// CompactOverride defines override configuration for compact backup at different levels
// Priority: cluster > project > tenant > region > global default
type CompactOverride struct {
	// Identification fields
	TenantID  string `mapstructure:"tenantId"`
	ProjectID string `mapstructure:"projectId"`
	ClusterID string `mapstructure:"clusterId"`
	Region    string `mapstructure:"region"`
	Level     string `mapstructure:"level"` // "cluster", "project", "tenant", "region"

	// Compact configuration fields (all optional)
	Cycle       string `mapstructure:"cycle"`       // e.g., "5h50m"
	Concurrency *int   `mapstructure:"concurrency"` // e.g., 32
	CleanPolicy string `mapstructure:"cleanPolicy"` // e.g., "Retain" or "Delete"
	SyncTimeout string `mapstructure:"syncTimeout"` // e.g., "5h"
	MinStartTS  string `mapstructure:"minStartTS"`  // e.g., "1234567890"

	// ShardCount enables sharded compaction mode. Must be >= 0 when set.
	ShardCount *int `mapstructure:"shardCount"`

	// Image tag overrides (optional). Only the tag portion is overridden; the base image remains unchanged.
	BRImageTag   string `mapstructure:"brImageTag"`   // Override BR image tag, e.g., "v8.5.3"
	TiKVImageTag string `mapstructure:"tikvImageTag"` // Override TiKV image tag, e.g., "v8.5.3"
}

// CompactOverrideConfig contains all override configurations for compact backup
type CompactOverrideConfig struct {
	// Region-level defaults
	RegionDefaults map[string]CompactOverride `mapstructure:"regionDefaults"`

	// Customer-specific overrides (tenant/project/cluster level)
	CustomerOverrides []CompactOverride `mapstructure:"customerOverrides"`
}

// getStringOverride is a generic helper function to get string configuration with priority-based override
// Priority: cluster > project > tenant > region > global default
func (c *CompactConfig) getStringOverride(
	clusterID, tenantID, projectID uint64,
	region string,
	getField func(*CompactOverride) string,
	globalDefault string,
) string {
	if c.CompactOverrides == nil {
		return globalDefault
	}

	clusterIDStr := fmt.Sprintf("%d", clusterID)
	tenantIDStr := fmt.Sprintf("%d", tenantID)
	projectIDStr := fmt.Sprintf("%d", projectID)

	// 1. First check all cluster-level overrides (highest priority)
	for _, override := range c.CompactOverrides.CustomerOverrides {
		if override.Level == "cluster" &&
			override.ClusterID == clusterIDStr {
			if val := getField(&override); val != "" {
				return val
			}
		}
	}

	// 2. Then check project-level overrides
	for _, override := range c.CompactOverrides.CustomerOverrides {
		if override.Level == "project" &&
			override.ProjectID == projectIDStr &&
			override.Region == region {
			if val := getField(&override); val != "" {
				return val
			}
		}
	}

	// 3. Then check tenant-level overrides
	for _, override := range c.CompactOverrides.CustomerOverrides {
		if override.Level == "tenant" &&
			override.TenantID == tenantIDStr &&
			override.Region == region {
			if val := getField(&override); val != "" {
				return val
			}
		}
	}

	// 4. Check region default
	if regionConfig, ok := c.CompactOverrides.RegionDefaults[region]; ok {
		if val := getField(&regionConfig); val != "" {
			return val
		}
	}

	// 5. Fall back to global default
	return globalDefault
}

// getIntPtrOverride is a generic helper function to get *int configuration with priority-based override
func (c *CompactConfig) getIntPtrOverride(
	clusterID, tenantID, projectID uint64,
	region string,
	getField func(*CompactOverride) *int,
	globalDefault int,
) int {
	if c.CompactOverrides == nil {
		return globalDefault
	}

	clusterIDStr := fmt.Sprintf("%d", clusterID)
	tenantIDStr := fmt.Sprintf("%d", tenantID)
	projectIDStr := fmt.Sprintf("%d", projectID)

	// 1. First check all cluster-level overrides (highest priority)
	for _, override := range c.CompactOverrides.CustomerOverrides {
		if override.Level == "cluster" &&
			override.ClusterID == clusterIDStr {
			if val := getField(&override); val != nil {
				return *val
			}
		}
	}

	// 2. Then check project-level overrides
	for _, override := range c.CompactOverrides.CustomerOverrides {
		if override.Level == "project" &&
			override.ProjectID == projectIDStr &&
			override.Region == region {
			if val := getField(&override); val != nil {
				return *val
			}
		}
	}

	// 3. Then check tenant-level overrides
	for _, override := range c.CompactOverrides.CustomerOverrides {
		if override.Level == "tenant" &&
			override.TenantID == tenantIDStr &&
			override.Region == region {
			if val := getField(&override); val != nil {
				return *val
			}
		}
	}

	// 4. Check region default
	if regionConfig, ok := c.CompactOverrides.RegionDefaults[region]; ok {
		if val := getField(&regionConfig); val != nil {
			return *val
		}
	}

	// 5. Fall back to global default
	return globalDefault
}

// GetCompactCycle returns the compact backup cycle with priority-based override
func (c *CompactConfig) GetCompactCycle(clusterID, tenantID, projectID uint64, region string) string {
	return c.getStringOverride(
		clusterID, tenantID, projectID, region,
		func(o *CompactOverride) string { return o.Cycle },
		c.CompactBackupCycle,
	)
}

// GetCompactCycleDuration returns the compact backup cycle duration with overrides
func (c *CompactConfig) GetCompactCycleDuration(clusterID, tenantID, projectID uint64, region string) (time.Duration, error) {
	cycleStr := c.GetCompactCycle(clusterID, tenantID, projectID, region)
	return time.ParseDuration(cycleStr)
}

// GetShardCount returns the compact backup shard count with priority-based override
// returns -1 when no override is set
func (c *CompactConfig) GetShardCount(clusterID, tenantID, projectID uint64, region string) int {
	return c.getIntPtrOverride(
		clusterID, tenantID, projectID, region,
		func(o *CompactOverride) *int { return o.ShardCount },
		-1, // -1 means no override
	)
}

// GetCompactConcurrency returns the compact backup concurrency with priority-based override
func (c *CompactConfig) GetCompactConcurrency(clusterID, tenantID, projectID uint64, region string) int {
	return c.getIntPtrOverride(
		clusterID, tenantID, projectID, region,
		func(o *CompactOverride) *int { return o.Concurrency },
		c.CompactBackupConcurrency,
	)
}

// GetCompactCleanPolicy returns the compact backup clean policy with priority-based override
func (c *CompactConfig) GetCompactCleanPolicy(clusterID, tenantID, projectID uint64, region string) string {
	return c.getStringOverride(
		clusterID, tenantID, projectID, region,
		func(o *CompactOverride) string { return o.CleanPolicy },
		c.CompactBackupCleanPolicy,
	)
}

// GetCompactSyncTimeout returns the compact backup sync timeout with priority-based override
func (c *CompactConfig) GetCompactSyncTimeout(clusterID, tenantID, projectID uint64, region string) string {
	return c.getStringOverride(
		clusterID, tenantID, projectID, region,
		func(o *CompactOverride) string { return o.SyncTimeout },
		c.CompactSyncTimeout,
	)
}

// GetCompactSyncTimeoutDurationWithOverride returns the compact backup sync timeout duration with overrides
func (c *CompactConfig) GetCompactSyncTimeoutDurationWithOverride(clusterID, tenantID, projectID uint64, region string) (time.Duration, error) {
	timeoutStr := c.GetCompactSyncTimeout(clusterID, tenantID, projectID, region)
	return time.ParseDuration(timeoutStr)
}

// GetCompactMinStartTSWithOverride returns the compact backup minimum start TS with priority-based override
// This function needs special handling because it requires parsing and returns a pointer
func (c *CompactConfig) GetCompactMinStartTSWithOverride(clusterID, tenantID, projectID uint64, region string) (*uint64, error) {
	minStartTSStr := c.getStringOverride(
		clusterID, tenantID, projectID, region,
		func(o *CompactOverride) string { return o.MinStartTS },
		c.CompactMinStartTS,
	)

	// Parse and return
	if minStartTSStr == "" {
		return nil, nil
	}

	ts, err := strconv.ParseUint(minStartTSStr, 10, 64)
	if err != nil {
		return nil, err
	}

	return &ts, nil
}

// GetBRImageTag returns the BR image tag override with priority-based override.
// Returns "" when no override is configured (caller should leave the tag unchanged).
func (c *CompactConfig) GetBRImageTag(clusterID, tenantID, projectID uint64, region string) string {
	return c.getStringOverride(
		clusterID, tenantID, projectID, region,
		func(o *CompactOverride) string { return o.BRImageTag },
		"",
	)
}

// GetTiKVImageTag returns the TiKV image tag override with priority-based override.
// Returns "" when no override is configured (caller should leave the tag unchanged).
func (c *CompactConfig) GetTiKVImageTag(clusterID, tenantID, projectID uint64, region string) string {
	return c.getStringOverride(
		clusterID, tenantID, projectID, region,
		func(o *CompactOverride) string { return o.TiKVImageTag },
		"",
	)
}
