package exportproxy

import (
	"context"
	"errors"
	"io"
	"log/slog"
	"path/filepath"
	"testing"

	"vocat/internal/store"
)

func TestManagerPersistsAndDeletesDisabledConfig(t *testing.T) {
	ctx := context.Background()
	database, err := store.Open(ctx, filepath.Join(t.TempDir(), "vocat.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer database.Close()
	if err := database.UpsertDevice(ctx, store.Device{ID: "modem-1", Name: "modem-1", Interface: "wwan0"}); err != nil {
		t.Fatal(err)
	}
	manager, err := New(ctx, database, slog.New(slog.NewTextHandler(io.Discard, nil)), "")
	if err != nil {
		t.Fatal(err)
	}
	created, err := manager.Create(ctx, Config{DeviceID: "modem-1", Mode: "socks5", ListenHost: "127.0.0.1", ListenPort: 1080})
	if err != nil {
		t.Fatal(err)
	}
	if created.ID == "" || created.Interface != "wwan0" {
		t.Fatalf("created = %+v", created)
	}
	configs, err := manager.Configs()
	if err != nil || len(configs) != 1 {
		t.Fatalf("configs = %+v, %v", configs, err)
	}
	if err := manager.DeleteAllAndDisable(ctx); err != nil {
		t.Fatal(err)
	}
	if _, err := manager.Configs(); !errors.Is(err, ErrDisabled) {
		t.Fatalf("Configs after disable = %v", err)
	}
	if _, err := database.AppSetting(ctx, SettingKey); !errors.Is(err, store.ErrNotFound) {
		t.Fatalf("setting remains: %v", err)
	}
}

func TestManagerRequiresRoamingDataForEnabledProxy(t *testing.T) {
	ctx := context.Background()
	database, err := store.Open(ctx, filepath.Join(t.TempDir(), "vocat.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer database.Close()
	if err := database.UpsertDevice(ctx, store.Device{ID: "modem-1", Name: "modem-1", Interface: "wwan0"}); err != nil {
		t.Fatal(err)
	}
	manager, err := New(ctx, database, nil, "")
	if err != nil {
		t.Fatal(err)
	}
	defer manager.Close()
	_, err = manager.Create(ctx, Config{DeviceID: "modem-1", Mode: "socks5", ListenHost: "127.0.0.1", ListenPort: 1080, Enabled: true})
	if err == nil {
		t.Fatal("enabled proxy was accepted while roaming data was disabled")
	}
}

func TestManagerEnabledConfigForDevice(t *testing.T) {
	ctx := context.Background()
	database, err := store.Open(ctx, filepath.Join(t.TempDir(), "vocat.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer database.Close()
	if err := database.UpsertDevice(ctx, store.Device{ID: "modem-1", Name: "modem-1", Interface: "wwan0", NetworkEnabled: true}); err != nil {
		t.Fatal(err)
	}
	if err := database.UpsertDevice(ctx, store.Device{ID: "modem-2", Name: "modem-2", Interface: "wwan1", NetworkEnabled: true}); err != nil {
		t.Fatal(err)
	}
	manager, err := New(ctx, database, nil, "")
	if err != nil {
		t.Fatal(err)
	}
	defer manager.Close()
	if _, ok := manager.EnabledConfigForDevice("modem-1"); ok {
		t.Fatal("reported an enabled config before any was created")
	}
	// A disabled config bound to modem-1 must not count.
	if _, err := manager.Create(ctx, Config{DeviceID: "modem-1", Mode: "socks5", ListenHost: "127.0.0.1", ListenPort: 1080}); err != nil {
		t.Fatal(err)
	}
	if _, ok := manager.EnabledConfigForDevice("modem-1"); ok {
		t.Fatal("disabled config counted as enabled")
	}
	// An enabled config bound to modem-2 counts only for modem-2. The listener start
	// is Linux-only, so the config is created disabled and flipped on in memory to
	// exercise the query without binding a port.
	created, err := manager.Create(ctx, Config{DeviceID: "modem-2", Mode: "socks5", ListenHost: "127.0.0.1", ListenPort: 0, AuthEnabled: true, Username: "u", Password: "secret"})
	if err != nil {
		t.Fatal(err)
	}
	manager.mu.Lock()
	for index := range manager.configs {
		if manager.configs[index].ID == created.ID {
			manager.configs[index].Enabled = true
		}
	}
	manager.mu.Unlock()
	if _, ok := manager.EnabledConfigForDevice("modem-1"); ok {
		t.Fatal("config bound to another device counted")
	}
	found, ok := manager.EnabledConfigForDevice("modem-2")
	if !ok {
		t.Fatal("enabled config not found for its device")
	}
	if found.Password != PasswordMask {
		t.Fatalf("password not redacted: %+v", found)
	}
}
