package service

import (
	"testing"
	"time"

	"github.com/mhsanaei/3x-ui/v3/internal/database"
	"github.com/mhsanaei/3x-ui/v3/internal/database/model"
	"github.com/mhsanaei/3x-ui/v3/internal/xray"
)

// TestAutoRenewClients_MultiInbound covers the renew loop across more than one
// inbound: every expired client with reset>0 must get a fresh future expiry,
// zeroed usage and re-enabled state, while a non-expiring client is untouched.
// It also guards the map-lookup refactor of the old quadratic inner loop.
func TestAutoRenewClients_MultiInbound(t *testing.T) {
	setupBulkDB(t)
	svc := &InboundService{}
	db := database.GetDB()

	past := time.Now().Add(-48 * time.Hour).UnixMilli()
	future := time.Now().Add(365 * 24 * time.Hour).UnixMilli()

	// Two inbounds, two expiring clients each, plus one client that never expires.
	ib1Clients := []model.Client{
		{Email: "a@x", ID: "11111111-1111-1111-1111-111111111111", Enable: false, Reset: 30, ExpiryTime: past},
		{Email: "b@x", ID: "22222222-2222-2222-2222-222222222222", Enable: false, Reset: 30, ExpiryTime: past},
	}
	ib2Clients := []model.Client{
		{Email: "c@x", ID: "33333333-3333-3333-3333-333333333333", Enable: false, Reset: 7, ExpiryTime: past},
		{Email: "keep@x", ID: "44444444-4444-4444-4444-444444444444", Enable: true, Reset: 0, ExpiryTime: future},
	}

	ib1 := mkInbound(t, 30001, model.VLESS, clientsSettings(t, ib1Clients))
	ib2 := mkInbound(t, 30002, model.VLESS, clientsSettings(t, ib2Clients))
	if err := svc.clientService.SyncInbound(nil, ib1.Id, ib1Clients); err != nil {
		t.Fatalf("SyncInbound ib1: %v", err)
	}
	if err := svc.clientService.SyncInbound(nil, ib2.Id, ib2Clients); err != nil {
		t.Fatalf("SyncInbound ib2: %v", err)
	}

	// Seed traffic rows: expired+depleted for the three renewable clients, and a
	// healthy row for keep@x.
	rows := []xray.ClientTraffic{
		{InboundId: ib1.Id, Email: "a@x", Enable: false, Up: 100, Down: 200, Reset: 30, ExpiryTime: past},
		{InboundId: ib1.Id, Email: "b@x", Enable: false, Up: 300, Down: 400, Reset: 30, ExpiryTime: past},
		{InboundId: ib2.Id, Email: "c@x", Enable: false, Up: 500, Down: 600, Reset: 7, ExpiryTime: past},
		{InboundId: ib2.Id, Email: "keep@x", Enable: true, Up: 1, Down: 2, Reset: 0, ExpiryTime: future},
	}
	if err := db.Create(&rows).Error; err != nil {
		t.Fatalf("seed client_traffics: %v", err)
	}

	if _, count, err := svc.autoRenewClients(db, newTrafficMutationBatch()); err != nil {
		t.Fatalf("autoRenewClients: %v", err)
	} else if count != 3 {
		t.Fatalf("renewed count = %d, want 3", count)
	}

	now := time.Now().UnixMilli()
	for _, email := range []string{"a@x", "b@x", "c@x"} {
		var row xray.ClientTraffic
		if err := db.Where("email = ?", email).First(&row).Error; err != nil {
			t.Fatalf("read %s: %v", email, err)
		}
		if row.Up != 0 || row.Down != 0 {
			t.Errorf("%s: usage not reset: up=%d down=%d", email, row.Up, row.Down)
		}
		if !row.Enable {
			t.Errorf("%s: not re-enabled", email)
		}
		if row.ExpiryTime <= now {
			t.Errorf("%s: expiry not advanced: got %d, now %d", email, row.ExpiryTime, now)
		}
	}

	// The non-expiring client must be left exactly as seeded.
	var keep xray.ClientTraffic
	if err := db.Where("email = ?", "keep@x").First(&keep).Error; err != nil {
		t.Fatalf("read keep@x: %v", err)
	}
	if keep.Up != 1 || keep.Down != 2 || keep.ExpiryTime != future {
		t.Errorf("keep@x was modified: %+v", keep)
	}

	// The renewed state must also be reflected in the inbound settings JSON.
	reloaded, err := svc.GetInbound(ib1.Id)
	if err != nil {
		t.Fatalf("GetInbound ib1: %v", err)
	}
	cs, err := svc.GetClients(reloaded)
	if err != nil {
		t.Fatalf("GetClients ib1: %v", err)
	}
	for _, c := range cs {
		if !c.Enable {
			t.Errorf("settings client %s still disabled after renew", c.Email)
		}
		if c.ExpiryTime <= now {
			t.Errorf("settings client %s expiry not advanced: %d", c.Email, c.ExpiryTime)
		}
	}
}
