package service

import (
	"encoding/json"
	"path/filepath"
	"testing"

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

	"gorm.io/gorm"
)

const visionTest = "xtls-rprx-vision"

func clientFlowsInSettings(t *testing.T, settings string) map[string]string {
	t.Helper()
	var parsed map[string]any
	if err := json.Unmarshal([]byte(settings), &parsed); err != nil {
		t.Fatalf("parse settings: %v", err)
	}
	out := map[string]string{}
	clients, _ := parsed["clients"].([]any)
	for _, c := range clients {
		cm, ok := c.(map[string]any)
		if !ok {
			continue
		}
		email, _ := cm["email"].(string)
		flow, _ := cm["flow"].(string)
		out[email] = flow
	}
	return out
}

func TestStripClientFlows(t *testing.T) {
	cases := []struct {
		name        string
		in          string
		wantChanged bool
		wantFlows   map[string]string
	}{
		{
			name:        "clears vision on all clients",
			in:          `{"clients":[{"email":"a","flow":"` + visionTest + `"},{"email":"b","flow":"` + visionTest + `"}]}`,
			wantChanged: true,
			wantFlows:   map[string]string{"a": "", "b": ""},
		},
		{
			name:        "mixed flows: clears only the non-empty",
			in:          `{"clients":[{"email":"a","flow":"` + visionTest + `"},{"email":"b","flow":""}]}`,
			wantChanged: true,
			wantFlows:   map[string]string{"a": "", "b": ""},
		},
		{
			name:        "no flows: unchanged",
			in:          `{"clients":[{"email":"a","flow":""},{"email":"b"}]}`,
			wantChanged: false,
			wantFlows:   map[string]string{"a": "", "b": ""},
		},
		{
			name:        "no clients: unchanged",
			in:          `{"decryption":"none"}`,
			wantChanged: false,
		},
		{
			name:        "malformed json: unchanged",
			in:          `{not json`,
			wantChanged: false,
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			out, changed := stripClientFlows(tc.in)
			if changed != tc.wantChanged {
				t.Fatalf("changed = %v, want %v", changed, tc.wantChanged)
			}
			if !changed {
				if out != tc.in {
					t.Fatalf("unchanged input must be returned verbatim, got %q", out)
				}
				return
			}
			got := clientFlowsInSettings(t, out)
			for email, want := range tc.wantFlows {
				if got[email] != want {
					t.Errorf("flow[%s] = %q, want %q", email, got[email], want)
				}
			}
		})
	}
}

func initFlowTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	dbDir := t.TempDir()
	t.Setenv("XUI_DB_FOLDER", dbDir)
	if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
		t.Fatalf("InitDB: %v", err)
	}
	t.Cleanup(func() { _ = database.CloseDB() })
	return database.GetDB()
}

func TestAddInbound_DisableFlowClampsClientFlow(t *testing.T) {
	initFlowTestDB(t)
	ibSvc := &InboundService{}

	in := &model.Inbound{
		Tag: "dis-add", Enable: true, Port: 52001, Protocol: model.VLESS,
		StreamSettings: `{"network":"tcp","security":"reality"}`,
		Settings:       `{"clients":[{"id":"u1","email":"a@x","flow":"` + visionTest + `","subId":"s1","enable":true}]}`,
		DisableFlow:    true,
	}
	if _, _, err := ibSvc.AddInbound(in); err != nil {
		t.Fatalf("AddInbound: %v", err)
	}

	got, err := ibSvc.GetInbound(in.Id)
	if err != nil {
		t.Fatalf("GetInbound: %v", err)
	}
	if !got.DisableFlow {
		t.Error("DisableFlow not persisted on created inbound")
	}
	if f := clientFlowsInSettings(t, got.Settings)["a@x"]; f != "" {
		t.Errorf("settings flow = %q, want empty (clamped at creation)", f)
	}
	list, err := ibSvc.clientService.ListForInbound(nil, in.Id)
	if err != nil {
		t.Fatalf("ListForInbound: %v", err)
	}
	if len(list) != 1 || list[0].Flow != "" {
		t.Errorf("flow_override = %#v, want empty (xray must not expect Vision)", list)
	}
}

func TestUpdateInbound_DisableFlowPersistsStripsAndResistsRestore(t *testing.T) {
	db := initFlowTestDB(t)
	ibSvc := &InboundService{}
	cs := &ClientService{}

	const email = "shared@x"
	const uid = "ce8d33df-3a64-4f10-8f9b-91c3a8e0d001"

	sibling := &model.Inbound{
		Tag: "sib", Enable: true, Port: 52101, Protocol: model.VLESS,
		StreamSettings: `{"network":"tcp","security":"reality"}`,
		Settings:       `{"clients":[{"id":"` + uid + `","email":"` + email + `","flow":"` + visionTest + `","subId":"s1","enable":true}]}`,
	}
	if err := db.Create(sibling).Error; err != nil {
		t.Fatalf("create sibling: %v", err)
	}
	sc, _ := ibSvc.GetClients(sibling)
	if err := cs.SyncInbound(nil, sibling.Id, sc); err != nil {
		t.Fatalf("sync sibling: %v", err)
	}

	target := &model.Inbound{
		Tag: "tgt", Enable: true, Port: 52102, Protocol: model.VLESS,
		StreamSettings: `{"network":"tcp","security":"reality"}`,
		Settings:       `{"clients":[{"id":"` + uid + `","email":"` + email + `","flow":"` + visionTest + `","subId":"s1","enable":true}]}`,
	}
	if err := db.Create(target).Error; err != nil {
		t.Fatalf("create target: %v", err)
	}
	tc, _ := ibSvc.GetClients(target)
	if err := cs.SyncInbound(nil, target.Id, tc); err != nil {
		t.Fatalf("sync target: %v", err)
	}

	upd := *target
	upd.DisableFlow = true
	if _, _, err := ibSvc.UpdateInbound(&upd); err != nil {
		t.Fatalf("UpdateInbound: %v", err)
	}

	reloaded, err := ibSvc.GetInbound(target.Id)
	if err != nil {
		t.Fatalf("GetInbound: %v", err)
	}
	if !reloaded.DisableFlow {
		t.Fatal("DisableFlow did not persist through UpdateInbound (blocking regression)")
	}
	if f := clientFlowsInSettings(t, reloaded.Settings)["shared@x"]; f != "" {
		t.Errorf("target settings flow = %q, want empty after disable", f)
	}
	list, err := cs.ListForInbound(nil, target.Id)
	if err != nil {
		t.Fatalf("ListForInbound(target): %v", err)
	}
	if len(list) != 1 || list[0].Flow != "" {
		t.Errorf("target flow_override = %#v, want empty", list)
	}

	ibSvc.MigrationRestoreVisionFlow()
	reloaded2, err := ibSvc.GetInbound(target.Id)
	if err != nil {
		t.Fatalf("GetInbound after restore: %v", err)
	}
	if f := clientFlowsInSettings(t, reloaded2.Settings)["shared@x"]; f != "" {
		t.Errorf("after MigrationRestoreVisionFlow target flow = %q, want empty (must not self-revert)", f)
	}
	sList, err := cs.ListForInbound(nil, sibling.Id)
	if err != nil {
		t.Fatalf("ListForInbound(sibling): %v", err)
	}
	if len(sList) != 1 || sList[0].Flow != visionTest {
		t.Errorf("sibling flow_override = %#v, want Vision preserved", sList)
	}
}
