package backbeat

import (
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"
)

// HLC implements Hybrid Logical Clock for BACKBEAT-REQ-003 (degrade local)
// Provides ordering guarantees for distributed events and supports reconciliation
type HLC struct {
	mu       sync.RWMutex
	pt       time.Time // physical time
	lc       int64     // logical counter
	nodeID   string    // node identifier for uniqueness
	lastSync time.Time // last successful sync with leader
}

// NewHLC creates a new Hybrid Logical Clock instance
func NewHLC(nodeID string) *HLC {
	return &HLC{
		pt:       time.Now().UTC(),
		lc:       0,
		nodeID:   nodeID,
		lastSync: time.Now().UTC(),
	}
}

// Next generates the next HLC timestamp
// Format: unix_ms_hex:logical_counter_hex:node_id_suffix
// Example: "7ffd:0001:abcd"
func (h *HLC) Next() string {
	h.mu.Lock()
	defer h.mu.Unlock()

	now := time.Now().UTC()

	// BACKBEAT-REQ-003: Support for local time derivation
	if now.After(h.pt) || now.Equal(h.pt) {
		h.pt = now
		if now.After(h.pt) {
			h.lc = 0
		} else {
			h.lc++
		}
	} else {
		h.lc++
	}

	// Format as compact hex representation
	ptMs := h.pt.UnixMilli()
	nodeHash := h.nodeID
	if len(nodeHash) > 4 {
		nodeHash = nodeHash[:4]
	}

	return fmt.Sprintf("%04x:%04x:%s", ptMs&0xFFFF, h.lc&0xFFFF, nodeHash)
}

// Update synchronizes with an external HLC timestamp
// Used for BACKBEAT-REQ-003 reconciliation with leader
func (h *HLC) Update(remoteHLC string) error {
	h.mu.Lock()
	defer h.mu.Unlock()

	parts := strings.Split(remoteHLC, ":")
	if len(parts) != 3 {
		return fmt.Errorf("invalid HLC format: %s", remoteHLC)
	}

	remotePt, err := strconv.ParseInt(parts[0], 16, 64)
	if err != nil {
		return fmt.Errorf("invalid physical time in HLC: %v", err)
	}

	remoteLc, err := strconv.ParseInt(parts[1], 16, 64)
	if err != nil {
		return fmt.Errorf("invalid logical counter in HLC: %v", err)
	}

	now := time.Now().UTC()
	remoteTime := time.UnixMilli(remotePt)

	// Update physical time to max(local_time, remote_time, current_time)
	maxTime := now
	if remoteTime.After(maxTime) {
		maxTime = remoteTime
	}
	if h.pt.After(maxTime) {
		maxTime = h.pt
	}

	// Update logical counter based on HLC algorithm
	if maxTime.Equal(h.pt) && maxTime.Equal(remoteTime) {
		h.lc = max(h.lc, remoteLc) + 1
	} else if maxTime.Equal(h.pt) {
		h.lc++
	} else if maxTime.Equal(remoteTime) {
		h.lc = remoteLc + 1
	} else {
		h.lc = 0
	}

	h.pt = maxTime
	h.lastSync = now
	return nil
}

// GetDrift returns the time since last successful sync with leader
// Used for BACKBEAT-PER-003 (SDK timer drift ≤ 1% over 1 hour)
func (h *HLC) GetDrift() time.Duration {
	h.mu.RLock()
	defer h.mu.RUnlock()
	return time.Since(h.lastSync)
}

// Compare compares two HLC timestamps
// Returns -1 if a < b, 0 if a == b, 1 if a > b
func (h *HLC) Compare(a, b string) int {
	partsA := strings.Split(a, ":")
	partsB := strings.Split(b, ":")

	if len(partsA) != 3 || len(partsB) != 3 {
		return 0 // Invalid format, consider equal
	}

	ptA, _ := strconv.ParseInt(partsA[0], 16, 64)
	ptB, _ := strconv.ParseInt(partsB[0], 16, 64)

	if ptA != ptB {
		if ptA < ptB {
			return -1
		}
		return 1
	}

	lcA, _ := strconv.ParseInt(partsA[1], 16, 64)
	lcB, _ := strconv.ParseInt(partsB[1], 16, 64)

	if lcA != lcB {
		if lcA < lcB {
			return -1
		}
		return 1
	}

	// If physical time and logical counter are equal, compare node IDs
	if partsA[2] != partsB[2] {
		if partsA[2] < partsB[2] {
			return -1
		}
		return 1
	}

	return 0
}

func max(a, b int64) int64 {
	if a > b {
		return a
	}
	return b
}
