package radar

import (
	"context"
	"net/http"
	"strings"
	"testing"

	"apps/backend/internal/middleware"
	radarRepo "apps/backend/internal/module/radar/repository"
	radarUC "apps/backend/internal/module/radar/usecase"
	"apps/backend/internal/svc"
	"apps/backend/internal/types"
)

func watchCtx(t *testing.T, uid int64, maxActive int, withProfile bool) (context.Context, *svc.ServiceContext) {
	t.Helper()
	radar := radarUC.New(radarRepo.NewMemory())
	radar.Quota = radarUC.FixedQuota{MaxActiveWatches: maxActive, MaxDailyOpportunities: 30}
	svcCtx := &svc.ServiceContext{Radar: radar}
	ctx := middleware.WithUID(context.Background(), uid)
	if withProfile {
		if _, err := NewUpsertServiceProfileLogic(ctx, svcCtx).UpsertServiceProfile(validProfileReq()); err != nil {
			t.Fatalf("seed profile: %v", err)
		}
	}
	return ctx, svcCtx
}

func createWatch(t *testing.T, ctx context.Context, svcCtx *svc.ServiceContext, term string, enabled bool) *types.RadarWatchPublic {
	t.Helper()
	w, err := NewCreateWatchLogic(ctx, svcCtx).CreateWatch(&types.CreateWatchReq{
		Terms:   []string{term},
		Enabled: enabled,
	})
	if err != nil {
		t.Fatalf("create watch %q: %v", term, err)
	}
	return w
}

func TestCreateActiveWatchWithoutProfileSucceeds(t *testing.T) {
	ctx, svcCtx := watchCtx(t, 42, 5, false)

	w, err := NewCreateWatchLogic(ctx, svcCtx).CreateWatch(&types.CreateWatchReq{
		Terms:   []string{"婚攝 推薦"},
		Enabled: true,
	})
	if err != nil {
		t.Fatalf("active watch without profile: %v", err)
	}
	if w.Status != "active" {
		t.Fatalf("status = %q, want active", w.Status)
	}
}

// RW-01：上限 1 時第二個 active 被拒，訊息要有目前上限與升級提示。
func TestCreateWatchOverQuotaCarriesLimitAndUpgradeHint(t *testing.T) {
	ctx, svcCtx := watchCtx(t, 42, 1, true)
	createWatch(t, ctx, svcCtx, "婚攝 推薦", true)

	_, err := NewCreateWatchLogic(ctx, svcCtx).CreateWatch(&types.CreateWatchReq{
		Terms:   []string{"活動紀錄"},
		Enabled: true,
	})
	if err == nil {
		t.Fatal("second active watch accepted on a 1-watch plan")
	}
	env := assertStatus(t, err, http.StatusBadRequest, 400100)
	if !strings.Contains(env.Message, "1") || !strings.Contains(env.Message, "upgrade") {
		t.Fatalf("message = %q, want the current limit plus an upgrade hint", env.Message)
	}
}

func TestListWatchesReportsQuotaAndProfileState(t *testing.T) {
	ctx, svcCtx := watchCtx(t, 42, 5, true)
	createWatch(t, ctx, svcCtx, "婚攝 推薦", true)
	createWatch(t, ctx, svcCtx, "活動紀錄", false)

	data, err := NewListWatchesLogic(ctx, svcCtx).ListWatches(&types.ListWatchesReq{Page: 1, PageSize: 20})
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(data.List) != 2 || data.Pagination.Total != 2 {
		t.Fatalf("list = %d items, total = %d", len(data.List), data.Pagination.Total)
	}
	// 前端要靠這三個值在建立之前就決定顯示引導還是升級提示。
	if data.ActiveCount != 1 || data.MaxActive != 5 || !data.ProfileExists {
		t.Fatalf("quota fields = active %d / max %d / profile %v", data.ActiveCount, data.MaxActive, data.ProfileExists)
	}

	paused, err := NewListWatchesLogic(ctx, svcCtx).ListWatches(&types.ListWatchesReq{Page: 1, PageSize: 20, Status: "paused"})
	if err != nil {
		t.Fatalf("list paused: %v", err)
	}
	if len(paused.List) != 1 || paused.List[0].Status != "paused" {
		t.Fatalf("status filter returned %+v", paused.List)
	}
}

// RW-02／RW-04：pause 退出 active 清單，archive 之後不能 resume。
func TestPauseResumeArchiveThroughHTTPLayer(t *testing.T) {
	ctx, svcCtx := watchCtx(t, 42, 5, true)
	w := createWatch(t, ctx, svcCtx, "婚攝 推薦", true)

	paused, err := NewPauseWatchLogic(ctx, svcCtx).PauseWatch(&types.WatchIdReq{Id: w.Id})
	if err != nil || paused.Status != "paused" {
		t.Fatalf("pause = %+v, err = %v", paused, err)
	}
	after, err := NewListWatchesLogic(ctx, svcCtx).ListWatches(&types.ListWatchesReq{Page: 1, PageSize: 20})
	if err != nil || after.ActiveCount != 0 {
		t.Fatalf("active_count = %d after pause (err %v)", after.ActiveCount, err)
	}

	resumed, err := NewResumeWatchLogic(ctx, svcCtx).ResumeWatch(&types.WatchIdReq{Id: w.Id})
	if err != nil || resumed.Status != "active" {
		t.Fatalf("resume = %+v, err = %v", resumed, err)
	}

	ok, err := NewArchiveWatchLogic(ctx, svcCtx).ArchiveWatch(&types.WatchIdReq{Id: w.Id})
	if err != nil || !ok.Ok {
		t.Fatalf("archive = %+v, err = %v", ok, err)
	}
	if _, err := NewResumeWatchLogic(ctx, svcCtx).ResumeWatch(&types.WatchIdReq{Id: w.Id}); err == nil {
		t.Fatal("archived watch was resumed")
	} else {
		assertStatus(t, err, http.StatusBadRequest, 400100)
	}
	// 軟刪：列表還看得到，只是不再是 active。
	list, err := NewListWatchesLogic(ctx, svcCtx).ListWatches(&types.ListWatchesReq{Page: 1, PageSize: 20})
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(list.List) != 1 || list.List[0].Status != "archived" || list.ActiveCount != 0 {
		t.Fatalf("archive should be a soft delete, got %+v", list.List)
	}
}

func TestUpdateWatchLeavesOmittedFieldsAlone(t *testing.T) {
	ctx, svcCtx := watchCtx(t, 42, 5, true)
	created, err := NewCreateWatchLogic(ctx, svcCtx).CreateWatch(&types.CreateWatchReq{
		Terms:        []string{"婚攝 推薦"},
		ExcludeTerms: []string{"徵才"},
		Regions:      []string{"TPE"},
		Enabled:      true,
	})
	if err != nil {
		t.Fatalf("create: %v", err)
	}

	updated, err := NewUpdateWatchLogic(ctx, svcCtx).UpdateWatch(&types.UpdateWatchReq{
		Id:      created.Id,
		Regions: []string{"KHH"},
	})
	if err != nil {
		t.Fatalf("update: %v", err)
	}
	if len(updated.Terms) != 1 || len(updated.ExcludeTerms) != 1 {
		t.Fatalf("omitted fields were cleared: %+v", updated)
	}
	if len(updated.Regions) != 1 || updated.Regions[0] != "KHH" {
		t.Fatalf("regions = %v", updated.Regions)
	}
}

func TestWatchEndpointsRejectOtherOwners(t *testing.T) {
	ctx, svcCtx := watchCtx(t, 42, 5, true)
	w := createWatch(t, ctx, svcCtx, "婚攝 推薦", true)

	other := middleware.WithUID(context.Background(), 43)
	for name, call := range map[string]func() error{
		"get": func() error {
			_, err := NewGetWatchLogic(other, svcCtx).GetWatch(&types.WatchIdReq{Id: w.Id})
			return err
		},
		"pause": func() error {
			_, err := NewPauseWatchLogic(other, svcCtx).PauseWatch(&types.WatchIdReq{Id: w.Id})
			return err
		},
		"resume": func() error {
			_, err := NewResumeWatchLogic(other, svcCtx).ResumeWatch(&types.WatchIdReq{Id: w.Id})
			return err
		},
		"archive": func() error {
			_, err := NewArchiveWatchLogic(other, svcCtx).ArchiveWatch(&types.WatchIdReq{Id: w.Id})
			return err
		},
		"update": func() error {
			_, err := NewUpdateWatchLogic(other, svcCtx).UpdateWatch(&types.UpdateWatchReq{Id: w.Id, Regions: []string{"KHH"}})
			return err
		},
	} {
		t.Run(name, func(t *testing.T) {
			err := call()
			if err == nil {
				t.Fatalf("owner 43 could %s owner 42's watch", name)
			}
			assertStatus(t, err, http.StatusForbidden, 403003)
		})
	}
}

func TestWatchEndpointsRequireAuth(t *testing.T) {
	_, svcCtx := watchCtx(t, 42, 5, true)
	anon := context.Background()

	if _, err := NewListWatchesLogic(anon, svcCtx).ListWatches(&types.ListWatchesReq{}); err == nil {
		t.Fatal("list without JWT succeeded")
	} else {
		assertStatus(t, err, http.StatusUnauthorized, 401001)
	}
	if _, err := NewCreateWatchLogic(anon, svcCtx).CreateWatch(&types.CreateWatchReq{Terms: []string{"婚攝"}}); err == nil {
		t.Fatal("create without JWT succeeded")
	} else {
		assertStatus(t, err, http.StatusUnauthorized, 401001)
	}
}

func TestMissingWatchReportsNotFound(t *testing.T) {
	ctx, svcCtx := watchCtx(t, 42, 5, true)
	if _, err := NewGetWatchLogic(ctx, svcCtx).GetWatch(&types.WatchIdReq{Id: "does-not-exist"}); err == nil {
		t.Fatal("missing watch returned success")
	} else {
		assertStatus(t, err, http.StatusNotFound, 404001)
	}
}
