package app

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

	"sub2api-cn-relay-manager/internal/store/sqlite"
)

func TestAPIListProviderAccountsReturnsRows(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		ListProviderAccounts: func(_ context.Context, req ListProviderAccountsRequest) ([]ProviderAccountInfo, error) {
			if req.ProviderID != "deepseek-official" {
				t.Fatalf("ProviderID = %q, want deepseek-official", req.ProviderID)
			}
			if req.LogicalGroupID != "gpt-shared" {
				t.Fatalf("LogicalGroupID = %q, want gpt-shared", req.LogicalGroupID)
			}
			if req.AccountStatus != "disabled" {
				t.Fatalf("AccountStatus = %q, want disabled", req.AccountStatus)
			}
			if req.BindingState != "conflict" {
				t.Fatalf("BindingState = %q, want conflict", req.BindingState)
			}
			return []ProviderAccountInfo{{
				ID:                    7,
				HostID:                "remote43",
				HostBaseURL:           "https://host.example.com",
				ProviderID:            "deepseek-official",
				ProviderName:          "DeepSeek Official",
				RouteID:               "route-1",
				RouteName:             "Primary Route",
				LogicalGroupID:        "gpt-shared",
				ShadowGroupID:         "group-9",
				ShadowHostID:          "remote43",
				HostAccountID:         "9",
				AccountName:           "deepseek-01",
				AccountStatus:         "disabled",
				BindingState:          "conflict",
				BindingCandidateCount: 2,
				DisabledReason:        "manual_disable",
				UpstreamBaseURLHint:   "https://api.deepseek.com",
			}}, nil
		},
	})

	request := httptestRequest(t, "GET", "/api/provider-accounts?provider_id=deepseek-official&logical_group_id=gpt-shared&account_status=disabled&binding_state=conflict", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, 200)
	var payload map[string][]ProviderAccountInfo
	if err := json.Unmarshal(response.Body().Bytes(), &payload); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	accounts := payload["provider_accounts"]
	if len(accounts) != 1 || accounts[0].ID != 7 || accounts[0].AccountStatus != "disabled" {
		t.Fatalf("provider_accounts = %+v, want one disabled row id=7", accounts)
	}
	if accounts[0].LogicalGroupID != "gpt-shared" || accounts[0].RouteName != "Primary Route" {
		t.Fatalf("provider_accounts relationship fields = %+v", accounts[0])
	}
}

func TestAPIGetProviderAccountBindingCandidatesUsesPathID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		GetProviderAccountBindingCandidates: func(_ context.Context, req GetProviderAccountBindingCandidatesRequest) (ProviderAccountBindingCandidatesResult, error) {
			if req.AccountID != 7 {
				t.Fatalf("AccountID = %d, want 7", req.AccountID)
			}
			return ProviderAccountBindingCandidatesResult{
				ProviderAccount: ProviderAccountInfo{ID: 7, BindingState: "conflict"},
				CandidateRoutes: []LogicalGroupRouteInfo{{RouteID: "route-a"}, {RouteID: "route-b"}},
			}, nil
		},
	})

	request := httptestRequest(t, "GET", "/api/provider-accounts/7/binding-candidates", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, 200)
	var payload struct {
		ProviderAccount ProviderAccountInfo     `json:"provider_account"`
		CandidateRoutes []LogicalGroupRouteInfo `json:"candidate_routes"`
	}
	if err := json.Unmarshal(response.Body().Bytes(), &payload); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	if payload.ProviderAccount.ID != 7 || len(payload.CandidateRoutes) != 2 || payload.CandidateRoutes[0].RouteID != "route-a" {
		t.Fatalf("binding candidates payload = %+v", payload)
	}
}

func TestAPIUpdateProviderAccountBindingUsesPathID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		UpdateProviderAccountBinding: func(_ context.Context, req UpdateProviderAccountBindingRequest) (ProviderAccountInfo, error) {
			if req.AccountID != 42 {
				t.Fatalf("AccountID = %d, want 42", req.AccountID)
			}
			if req.RouteID != "route-9" || req.Clear {
				t.Fatalf("request = %+v, want route-9 clear=false", req)
			}
			return ProviderAccountInfo{ID: req.AccountID, RouteID: req.RouteID, BindingState: "assigned"}, nil
		},
	})

	request := httptestRequest(t, "POST", "/api/provider-accounts/42/binding", map[string]any{"route_id": "route-9"}, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, 200)
	assertJSONContains(t, response.Body().Bytes(), "provider_account.route_id", "route-9")
}

func TestAPIDisableProviderAccountUsesPathID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		DisableProviderAccount: func(_ context.Context, req UpdateProviderAccountStatusRequest) (ProviderAccountInfo, error) {
			if req.AccountID != 42 {
				t.Fatalf("AccountID = %d, want 42", req.AccountID)
			}
			if req.AccountStatus != "disabled" {
				t.Fatalf("AccountStatus = %q, want disabled", req.AccountStatus)
			}
			if req.DisabledReason != "manual_disable" {
				t.Fatalf("DisabledReason = %q, want manual_disable", req.DisabledReason)
			}
			return ProviderAccountInfo{ID: req.AccountID, AccountStatus: req.AccountStatus, DisabledReason: req.DisabledReason}, nil
		},
	})

	request := httptestRequest(t, "POST", "/api/provider-accounts/42/disable", map[string]any{"reason": "manual_disable"}, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, 200)
	assertJSONContains(t, response.Body().Bytes(), "provider_account.id", float64(42))
	assertJSONContains(t, response.Body().Bytes(), "provider_account.account_status", "disabled")
}

func TestAPIEnableProviderAccountUsesPathID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		EnableProviderAccount: func(_ context.Context, req UpdateProviderAccountStatusRequest) (ProviderAccountInfo, error) {
			if req.AccountID != 42 {
				t.Fatalf("AccountID = %d, want 42", req.AccountID)
			}
			if req.AccountStatus != sqlite.ProviderAccountStatusActive {
				t.Fatalf("AccountStatus = %q, want active", req.AccountStatus)
			}
			return ProviderAccountInfo{ID: req.AccountID, AccountStatus: req.AccountStatus}, nil
		},
	})

	request := httptestRequest(t, "POST", "/api/provider-accounts/42/enable", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, 200)
	assertJSONContains(t, response.Body().Bytes(), "provider_account.id", float64(42))
	assertJSONContains(t, response.Body().Bytes(), "provider_account.account_status", sqlite.ProviderAccountStatusActive)
}

func TestAPIRetireProviderAccountUsesPathID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		RetireProviderAccount: func(_ context.Context, req UpdateProviderAccountStatusRequest) (ProviderAccountInfo, error) {
			if req.AccountID != 42 {
				t.Fatalf("AccountID = %d, want 42", req.AccountID)
			}
			if req.AccountStatus != sqlite.ProviderAccountStatusDeprecated {
				t.Fatalf("AccountStatus = %q, want deprecated", req.AccountStatus)
			}
			return ProviderAccountInfo{ID: req.AccountID, AccountStatus: req.AccountStatus}, nil
		},
	})

	request := httptestRequest(t, "POST", "/api/provider-accounts/42/retire", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, 200)
	assertJSONContains(t, response.Body().Bytes(), "provider_account.id", float64(42))
	assertJSONContains(t, response.Body().Bytes(), "provider_account.account_status", sqlite.ProviderAccountStatusDeprecated)
}

func TestNewActionSetProviderAccountListAndStatusFlow(t *testing.T) {
	dbPath := filepath.Join(t.TempDir(), "provider-accounts.db")
	dsn := "file:" + filepath.ToSlash(dbPath) + "?_busy_timeout=5000"
	actions := NewActionSet(dsn)
	ctx := context.Background()

	store, err := sqlite.Open(ctx, dsn)
	if err != nil {
		t.Fatalf("sqlite.Open() error = %v", err)
	}
	defer store.Close()

	hostID, err := store.Hosts().Create(ctx, sqlite.Host{
		HostID:              "remote43",
		BaseURL:             "https://host.example.com",
		HostVersion:         "0.1.129",
		CapabilityProbeJSON: `{"accounts":true}`,
		AuthType:            "apikey",
		AuthToken:           "host-key",
	})
	if err != nil {
		t.Fatalf("Hosts().Create() error = %v", err)
	}
	hostRow, err := store.Hosts().GetByID(ctx, hostID)
	if err != nil {
		t.Fatalf("Hosts().GetByID() error = %v", err)
	}
	packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "chk"})
	if err != nil {
		t.Fatalf("Packs().Create() error = %v", err)
	}
	providerRowID, err := store.Providers().Create(ctx, sqlite.Provider{
		PackID:      packID,
		ProviderID:  "deepseek-official",
		DisplayName: "DeepSeek Official",
		BaseURL:     "https://api.deepseek.com",
		Platform:    "openai",
	})
	if err != nil {
		t.Fatalf("Providers().Create() error = %v", err)
	}
	if _, err := store.LogicalGroups().Create(ctx, sqlite.LogicalGroup{
		LogicalGroupID:         "gpt-shared",
		DisplayName:            "GPT Shared",
		Status:                 "active",
		StickyMode:             "conversation_preferred",
		ConversationTTLSeconds: 7200,
		UserModelTTLSeconds:    1800,
		FailoverThreshold:      2,
		CooldownSeconds:        600,
	}); err != nil {
		t.Fatalf("LogicalGroups().Create() error = %v", err)
	}
	if _, err := store.LogicalGroupRoutes().Create(ctx, sqlite.LogicalGroupRoute{
		RouteID:             "route-1",
		LogicalGroupID:      "gpt-shared",
		Name:                "Primary Route",
		Status:              "active",
		Priority:            10,
		Weight:              100,
		ShadowGroupID:       "group-9",
		ShadowHostID:        "remote43",
		UpstreamBaseURLHint: "https://api.deepseek.com",
	}); err != nil {
		t.Fatalf("LogicalGroupRoutes().Create() error = %v", err)
	}
	providerAccountID, err := store.ProviderAccounts().Create(ctx, sqlite.ProviderAccount{
		HostID:          hostRow.ID,
		ProviderID:      providerRowID,
		RouteID:         "route-1",
		ShadowGroupID:   "group-9",
		HostAccountID:   "9",
		KeyFingerprint:  "sha256:abc",
		AccountName:     "deepseek-01",
		AccountStatus:   sqlite.ProviderAccountStatusActive,
		LastProbeStatus: "passed",
		LastProbeAt:     "2026-05-29T00:00:00Z",
	})
	if err != nil {
		t.Fatalf("ProviderAccounts().Create() error = %v", err)
	}

	listed, err := actions.ListProviderAccounts(ctx, ListProviderAccountsRequest{HostID: "remote43", ProviderID: "deepseek-official"})
	if err != nil {
		t.Fatalf("ListProviderAccounts() error = %v", err)
	}
	if len(listed) != 1 || listed[0].ID != providerAccountID {
		t.Fatalf("ListProviderAccounts() = %+v, want one row for id %d", listed, providerAccountID)
	}
	if listed[0].LogicalGroupID != "gpt-shared" || listed[0].RouteName != "Primary Route" || listed[0].ShadowHostID != "remote43" {
		t.Fatalf("ListProviderAccounts() relationship fields = %+v", listed[0])
	}
	if listed[0].BindingState != sqlite.ProviderAccountBindingStateAssigned || listed[0].BindingCandidateCount != 1 {
		t.Fatalf("ListProviderAccounts() binding view = %+v", listed[0])
	}

	candidates, err := actions.GetProviderAccountBindingCandidates(ctx, GetProviderAccountBindingCandidatesRequest{AccountID: providerAccountID})
	if err != nil {
		t.Fatalf("GetProviderAccountBindingCandidates() error = %v", err)
	}
	if len(candidates.CandidateRoutes) != 1 || candidates.CandidateRoutes[0].RouteID != "route-1" {
		t.Fatalf("GetProviderAccountBindingCandidates() = %+v", candidates)
	}

	if _, err := store.LogicalGroupRoutes().Create(ctx, sqlite.LogicalGroupRoute{
		RouteID:             "route-2",
		LogicalGroupID:      "gpt-shared",
		Name:                "Fallback Route",
		Status:              "active",
		Priority:            20,
		Weight:              100,
		ShadowGroupID:       "group-9",
		ShadowHostID:        "remote43",
		UpstreamBaseURLHint: "https://api.backup.example.com",
	}); err != nil {
		t.Fatalf("LogicalGroupRoutes().Create(route-2) error = %v", err)
	}

	updatedBinding, err := actions.UpdateProviderAccountBinding(ctx, UpdateProviderAccountBindingRequest{
		AccountID: providerAccountID,
		RouteID:   "route-2",
	})
	if err != nil {
		t.Fatalf("UpdateProviderAccountBinding() error = %v", err)
	}
	if updatedBinding.RouteID != "route-2" || updatedBinding.BindingState != sqlite.ProviderAccountBindingStateAssigned {
		t.Fatalf("UpdateProviderAccountBinding() = %+v", updatedBinding)
	}

	disabled, err := actions.DisableProviderAccount(ctx, UpdateProviderAccountStatusRequest{
		AccountID:      providerAccountID,
		DisabledReason: "manual_disable",
	})
	if err != nil {
		t.Fatalf("DisableProviderAccount() error = %v", err)
	}
	if disabled.AccountStatus != sqlite.ProviderAccountStatusDisabled || disabled.DisabledReason != "manual_disable" {
		t.Fatalf("DisableProviderAccount() = %+v", disabled)
	}

	enabled, err := actions.EnableProviderAccount(ctx, UpdateProviderAccountStatusRequest{AccountID: providerAccountID})
	if err != nil {
		t.Fatalf("EnableProviderAccount() error = %v", err)
	}
	if enabled.AccountStatus != sqlite.ProviderAccountStatusActive {
		t.Fatalf("EnableProviderAccount() = %+v, want active", enabled)
	}
}
