package sqlite

import (
	"context"
	"database/sql"
	"errors"
	"testing"
)

func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) {
	store := openTestDB(t)
	ctx := context.Background()

	id, err := store.LogicalGroups().Create(ctx, LogicalGroup{
		LogicalGroupID: "gpt-shared",
		DisplayName:    "GPT Shared",
		Status:         "active",
		Description:    "shared group",
	})
	if err != nil {
		t.Fatalf("Create() error = %v", err)
	}
	if id <= 0 {
		t.Fatalf("Create() id = %d, want positive", id)
	}

	group, err := store.LogicalGroups().GetByLogicalGroupID(ctx, "gpt-shared")
	if err != nil {
		t.Fatalf("GetByLogicalGroupID() error = %v", err)
	}
	if group.RoutePolicy != defaultLogicalGroupRoutePolicy {
		t.Fatalf("RoutePolicy = %q, want %q", group.RoutePolicy, defaultLogicalGroupRoutePolicy)
	}
	if group.StickyMode != defaultLogicalGroupStickyMode {
		t.Fatalf("StickyMode = %q, want %q", group.StickyMode, defaultLogicalGroupStickyMode)
	}

	if err := store.LogicalGroups().UpdateByLogicalGroupID(ctx, LogicalGroup{
		LogicalGroupID:         "gpt-shared",
		DisplayName:            "GPT Shared Updated",
		Status:                 "paused",
		Description:            "updated",
		RoutePolicy:            "priority",
		StickyMode:             "user_preferred",
		ConversationTTLSeconds: 3600,
		UserModelTTLSeconds:    900,
		FailoverThreshold:      3,
		CooldownSeconds:        120,
	}); err != nil {
		t.Fatalf("UpdateByLogicalGroupID() error = %v", err)
	}

	updated, err := store.LogicalGroups().GetByLogicalGroupID(ctx, "gpt-shared")
	if err != nil {
		t.Fatalf("GetByLogicalGroupID(updated) error = %v", err)
	}
	if updated.DisplayName != "GPT Shared Updated" || updated.Status != "paused" {
		t.Fatalf("updated group = %+v, want updated fields", updated)
	}

	if err := store.LogicalGroups().DeleteByLogicalGroupID(ctx, "gpt-shared"); err != nil {
		t.Fatalf("DeleteByLogicalGroupID() error = %v", err)
	}
	_, err = store.LogicalGroups().GetByLogicalGroupID(ctx, "gpt-shared")
	if !errors.Is(err, sql.ErrNoRows) {
		t.Fatalf("GetByLogicalGroupID() after delete error = %v, want sql.ErrNoRows", err)
	}
}

func TestLogicalGroupsRepoList(t *testing.T) {
	store := openTestDB(t)
	ctx := context.Background()

	for _, group := range []LogicalGroup{
		{LogicalGroupID: "group-a", DisplayName: "Group A", Status: "active"},
		{LogicalGroupID: "group-b", DisplayName: "Group B", Status: "active"},
	} {
		if _, err := store.LogicalGroups().Create(ctx, group); err != nil {
			t.Fatalf("Create(%q) error = %v", group.LogicalGroupID, err)
		}
	}

	groups, err := store.LogicalGroups().List(ctx)
	if err != nil {
		t.Fatalf("List() error = %v", err)
	}
	if len(groups) != 2 {
		t.Fatalf("List() len = %d, want 2", len(groups))
	}
	if groups[0].LogicalGroupID != "group-a" || groups[1].LogicalGroupID != "group-b" {
		t.Fatalf("List() = %+v, want insertion order", groups)
	}
}

func TestLogicalGroupModelsRepoCreateListDelete(t *testing.T) {
	store := openTestDBWithFK(t)
	ctx := context.Background()

	if _, err := store.LogicalGroups().Create(ctx, LogicalGroup{
		LogicalGroupID: "gpt-shared",
		DisplayName:    "GPT Shared",
		Status:         "active",
	}); err != nil {
		t.Fatalf("LogicalGroups().Create() error = %v", err)
	}

	if _, err := store.LogicalGroupModels().Create(ctx, LogicalGroupModel{
		LogicalGroupID: "gpt-shared",
		PublicModel:    "gpt-5.4",
	}); err != nil {
		t.Fatalf("LogicalGroupModels().Create() error = %v", err)
	}

	models, err := store.LogicalGroupModels().ListByLogicalGroupID(ctx, "gpt-shared")
	if err != nil {
		t.Fatalf("ListByLogicalGroupID() error = %v", err)
	}
	if len(models) != 1 || models[0].PublicModel != "gpt-5.4" {
		t.Fatalf("ListByLogicalGroupID() = %+v, want gpt-5.4", models)
	}

	if err := store.LogicalGroupModels().DeleteByLogicalGroupIDAndModel(ctx, "gpt-shared", "gpt-5.4"); err != nil {
		t.Fatalf("DeleteByLogicalGroupIDAndModel() error = %v", err)
	}
	models, err = store.LogicalGroupModels().ListByLogicalGroupID(ctx, "gpt-shared")
	if err != nil {
		t.Fatalf("ListByLogicalGroupID() after delete error = %v", err)
	}
	if len(models) != 0 {
		t.Fatalf("ListByLogicalGroupID() after delete len = %d, want 0", len(models))
	}
}

func TestLogicalGroupRoutesRepoCreateGetListUpdateDelete(t *testing.T) {
	store := openTestDBWithFK(t)
	ctx := context.Background()

	if _, err := store.LogicalGroups().Create(ctx, LogicalGroup{
		LogicalGroupID: "gpt-shared",
		DisplayName:    "GPT Shared",
		Status:         "active",
	}); err != nil {
		t.Fatalf("LogicalGroups().Create() error = %v", err)
	}

	if _, err := store.LogicalGroupRoutes().Create(ctx, LogicalGroupRoute{
		RouteID:        "asxs",
		LogicalGroupID: "gpt-shared",
		Name:           "ASXS",
		Status:         "active",
		Priority:       10,
		ShadowGroupID:  "gpt-shared__asxs",
		ShadowHostID:   "remote43",
	}); err != nil {
		t.Fatalf("LogicalGroupRoutes().Create() error = %v", err)
	}

	route, err := store.LogicalGroupRoutes().GetByRouteID(ctx, "asxs")
	if err != nil {
		t.Fatalf("GetByRouteID() error = %v", err)
	}
	if route.Weight != 100 {
		t.Fatalf("Weight = %d, want 100", route.Weight)
	}

	routes, err := store.LogicalGroupRoutes().ListByLogicalGroupID(ctx, "gpt-shared")
	if err != nil {
		t.Fatalf("ListByLogicalGroupID() error = %v", err)
	}
	if len(routes) != 1 || routes[0].RouteID != "asxs" {
		t.Fatalf("ListByLogicalGroupID() = %+v, want route asxs", routes)
	}

	if err := store.LogicalGroupRoutes().UpdateByRouteID(ctx, LogicalGroupRoute{
		RouteID:             "asxs",
		LogicalGroupID:      "gpt-shared",
		Name:                "ASXS Updated",
		Status:              "degraded",
		Priority:            20,
		Weight:              80,
		ShadowGroupID:       "gpt-shared__asxs",
		ShadowHostID:        "remote43",
		UpstreamBaseURLHint: "https://api.asxs.top/v1",
		CooldownUntil:       "2026-05-28T16:00:00Z",
	}); err != nil {
		t.Fatalf("UpdateByRouteID() error = %v", err)
	}

	updated, err := store.LogicalGroupRoutes().GetByRouteID(ctx, "asxs")
	if err != nil {
		t.Fatalf("GetByRouteID(updated) error = %v", err)
	}
	if updated.Name != "ASXS Updated" || updated.Status != "degraded" || updated.Weight != 80 {
		t.Fatalf("updated route = %+v, want updated fields", updated)
	}

	if err := store.LogicalGroupRoutes().DeleteByRouteID(ctx, "asxs"); err != nil {
		t.Fatalf("DeleteByRouteID() error = %v", err)
	}
	_, err = store.LogicalGroupRoutes().GetByRouteID(ctx, "asxs")
	if !errors.Is(err, sql.ErrNoRows) {
		t.Fatalf("GetByRouteID() after delete error = %v, want sql.ErrNoRows", err)
	}
}

func TestLogicalGroupRouteModelsRepoCreateList(t *testing.T) {
	store := openTestDBWithFK(t)
	ctx := context.Background()

	if _, err := store.LogicalGroups().Create(ctx, LogicalGroup{
		LogicalGroupID: "gpt-shared",
		DisplayName:    "GPT Shared",
		Status:         "active",
	}); err != nil {
		t.Fatalf("LogicalGroups().Create() error = %v", err)
	}
	if _, err := store.LogicalGroupRoutes().Create(ctx, LogicalGroupRoute{
		RouteID:        "codex2api",
		LogicalGroupID: "gpt-shared",
		Name:           "Codex2API",
		Status:         "active",
		Priority:       20,
		ShadowGroupID:  "gpt-shared__codex2api",
		ShadowHostID:   "remote43",
	}); err != nil {
		t.Fatalf("LogicalGroupRoutes().Create() error = %v", err)
	}

	if _, err := store.LogicalGroupRouteModels().Create(ctx, LogicalGroupRouteModel{
		RouteID:     "codex2api",
		PublicModel: "gpt-5.4",
	}); err != nil {
		t.Fatalf("LogicalGroupRouteModels().Create() error = %v", err)
	}

	models, err := store.LogicalGroupRouteModels().ListByRouteID(ctx, "codex2api")
	if err != nil {
		t.Fatalf("ListByRouteID() error = %v", err)
	}
	if len(models) != 1 {
		t.Fatalf("ListByRouteID() len = %d, want 1", len(models))
	}
	if models[0].ShadowModel != "gpt-5.4" {
		t.Fatalf("ShadowModel = %q, want default public model", models[0].ShadowModel)
	}
}

func TestLogicalGroupReposEnforceForeignKeysAndCascadeDelete(t *testing.T) {
	store := openTestDBWithFK(t)
	ctx := context.Background()

	if _, err := store.LogicalGroupModels().Create(ctx, LogicalGroupModel{
		LogicalGroupID: "missing-group",
		PublicModel:    "gpt-5.4",
	}); err == nil {
		t.Fatal("LogicalGroupModels().Create() error = nil, want foreign key failure")
	}

	if _, err := store.LogicalGroups().Create(ctx, LogicalGroup{
		LogicalGroupID: "gpt-shared",
		DisplayName:    "GPT Shared",
		Status:         "active",
	}); err != nil {
		t.Fatalf("LogicalGroups().Create() error = %v", err)
	}
	if _, err := store.LogicalGroupModels().Create(ctx, LogicalGroupModel{
		LogicalGroupID: "gpt-shared",
		PublicModel:    "gpt-5.4",
	}); err != nil {
		t.Fatalf("LogicalGroupModels().Create() error = %v", err)
	}
	if _, err := store.LogicalGroupRoutes().Create(ctx, LogicalGroupRoute{
		RouteID:        "asxs",
		LogicalGroupID: "gpt-shared",
		Name:           "ASXS",
		Status:         "active",
		Priority:       10,
		ShadowGroupID:  "gpt-shared__asxs",
		ShadowHostID:   "remote43",
	}); err != nil {
		t.Fatalf("LogicalGroupRoutes().Create() error = %v", err)
	}
	if _, err := store.LogicalGroupRouteModels().Create(ctx, LogicalGroupRouteModel{
		RouteID:     "asxs",
		PublicModel: "gpt-5.4",
	}); err != nil {
		t.Fatalf("LogicalGroupRouteModels().Create() error = %v", err)
	}

	if err := store.LogicalGroups().DeleteByLogicalGroupID(ctx, "gpt-shared"); err != nil {
		t.Fatalf("DeleteByLogicalGroupID() error = %v", err)
	}

	models, err := store.LogicalGroupModels().ListByLogicalGroupID(ctx, "gpt-shared")
	if err != nil {
		t.Fatalf("ListByLogicalGroupID() after cascade error = %v", err)
	}
	if len(models) != 0 {
		t.Fatalf("models after cascade len = %d, want 0", len(models))
	}
	routes, err := store.LogicalGroupRoutes().ListByLogicalGroupID(ctx, "gpt-shared")
	if err != nil {
		t.Fatalf("ListByLogicalGroupID(routes) after cascade error = %v", err)
	}
	if len(routes) != 0 {
		t.Fatalf("routes after cascade len = %d, want 0", len(routes))
	}
}
