package radar

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

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

func testCtx(uid int64) (context.Context, *svc.ServiceContext) {
	svcCtx := &svc.ServiceContext{Radar: radarUC.New(radarRepo.NewMemory())}
	return middleware.WithUID(context.Background(), uid), svcCtx
}

func validProfileReq() *types.UpsertServiceProfileReq {
	return &types.UpsertServiceProfileReq{
		Services:     []types.ServiceItem{{Name: "婚禮攝影", PriceMin: 18000, PriceMax: 36000, Currency: "TWD"}},
		Cases:        []types.ServiceCasePublic{{Title: "陽明山戶外婚禮"}},
		Forbidden:    []string{"保證接到案"},
		Faq:          []types.FaqItem{{Question: "可以加時嗎？", Answer: "可以"}},
		ServiceAreas: []string{"TPE"},
		Availability: "平日全天",
		ToneNote:     "親切、不推銷",
	}
}

// SP-02：填完服務檔案後 GET 要能讀回全部欄位，forbidden[] 尤其重要 —— 它是回覆生成的硬性過濾詞。
func TestServiceProfileRoundTripsThroughHTTPTypes(t *testing.T) {
	ctx, svcCtx := testCtx(42)

	if _, err := NewUpsertServiceProfileLogic(ctx, svcCtx).UpsertServiceProfile(validProfileReq()); err != nil {
		t.Fatalf("upsert: %v", err)
	}

	got, err := NewGetServiceProfileLogic(ctx, svcCtx).GetServiceProfile()
	if err != nil {
		t.Fatalf("get: %v", err)
	}
	if !got.Exists {
		t.Fatal("exists = false after upsert")
	}
	if len(got.Services) != 1 || got.Services[0].Name != "婚禮攝影" || got.Services[0].PriceMax != 36000 {
		t.Fatalf("services = %+v", got.Services)
	}
	if len(got.Forbidden) != 1 || got.Forbidden[0] != "保證接到案" {
		t.Fatalf("forbidden = %v", got.Forbidden)
	}
	if len(got.Faq) != 1 || len(got.Cases) != 1 || len(got.ServiceAreas) != 1 {
		t.Fatalf("faq/cases/areas lost: %+v", got)
	}
	if got.Availability != "平日全天" || got.ToneNote != "親切、不推銷" {
		t.Fatalf("free-text fields lost: %+v", got)
	}
	if got.UpdatedAt <= 0 {
		t.Fatalf("updated_at = %d, want ns timestamp", got.UpdatedAt)
	}
}

// 未建檔要回 exists=false 的 200，不是 404：表單得先開得起來才有東西填。
func TestGetBeforeFirstSaveReportsAbsentProfile(t *testing.T) {
	ctx, svcCtx := testCtx(42)

	got, err := NewGetServiceProfileLogic(ctx, svcCtx).GetServiceProfile()
	if err != nil {
		t.Fatalf("get: %v", err)
	}
	if got.Exists {
		t.Fatal("exists = true without any saved profile")
	}
	// 空陣列而非 null：前端直接 map，不必每個欄位判 null。
	if got.Services == nil || got.Cases == nil || got.Forbidden == nil || got.Faq == nil || got.ServiceAreas == nil {
		t.Fatalf("absent profile must use empty slices, got %+v", got)
	}
}

func TestServiceProfileIsolatedByJWTOwner(t *testing.T) {
	ctxA, svcCtx := testCtx(42)
	if _, err := NewUpsertServiceProfileLogic(ctxA, svcCtx).UpsertServiceProfile(validProfileReq()); err != nil {
		t.Fatalf("upsert as 42: %v", err)
	}

	// 同一個 store、另一個登入者：request 沒有任何欄位能指定 owner，所以看不到別人的檔案。
	ctxB := middleware.WithUID(context.Background(), 43)
	got, err := NewGetServiceProfileLogic(ctxB, svcCtx).GetServiceProfile()
	if err != nil {
		t.Fatalf("get as 43: %v", err)
	}
	if got.Exists {
		t.Fatal("owner 43 read owner 42's service profile")
	}

	// 43 覆寫自己的檔案不會動到 42 的。
	req := validProfileReq()
	req.Services[0].Name = "另一種服務"
	if _, err := NewUpsertServiceProfileLogic(ctxB, svcCtx).UpsertServiceProfile(req); err != nil {
		t.Fatalf("upsert as 43: %v", err)
	}
	back, err := NewGetServiceProfileLogic(ctxA, svcCtx).GetServiceProfile()
	if err != nil {
		t.Fatalf("get as 42: %v", err)
	}
	if back.Services[0].Name != "婚禮攝影" {
		t.Fatalf("owner 42's profile was overwritten by owner 43: %+v", back.Services)
	}
}

func TestServiceProfileRequiresAuth(t *testing.T) {
	svcCtx := &svc.ServiceContext{Radar: radarUC.New(radarRepo.NewMemory())}
	anon := context.Background()

	if _, err := NewGetServiceProfileLogic(anon, svcCtx).GetServiceProfile(); err == nil {
		t.Fatal("GET without JWT succeeded")
	} else {
		assertStatus(t, err, http.StatusUnauthorized, 401001)
	}
	if _, err := NewUpsertServiceProfileLogic(anon, svcCtx).UpsertServiceProfile(validProfileReq()); err == nil {
		t.Fatal("PUT without JWT succeeded")
	} else {
		assertStatus(t, err, http.StatusUnauthorized, 401001)
	}
}

// 驗證失敗要是明確的 400100＋能對到欄位的訊息，不是 500 也不是空成功。
func TestInvalidProfileMapsToValidationError(t *testing.T) {
	ctx, svcCtx := testCtx(42)

	req := validProfileReq()
	req.Services[0].PriceMin = 50000
	req.Services[0].PriceMax = 10000

	_, err := NewUpsertServiceProfileLogic(ctx, svcCtx).UpsertServiceProfile(req)
	if err == nil {
		t.Fatal("reversed price range was accepted")
	}
	env := assertStatus(t, err, http.StatusBadRequest, 400100)
	if env.Message == "" {
		t.Fatal("validation error carried no message")
	}
}

func assertStatus(t *testing.T, err error, wantHTTP int, wantCode int64) response.Envelope {
	t.Helper()
	rec := httptest.NewRecorder()
	response.Write(context.Background(), rec, nil, err)
	if rec.Code != wantHTTP {
		t.Fatalf("HTTP = %d, want %d (err=%v)", rec.Code, wantHTTP, err)
	}
	var env response.Envelope
	if decErr := json.NewDecoder(rec.Body).Decode(&env); decErr != nil {
		t.Fatalf("decode envelope: %v", decErr)
	}
	if env.Code != wantCode {
		t.Fatalf("code = %d, want %d", env.Code, wantCode)
	}
	return env
}
