package usecase

import (
	"context"
	"errors"
	"testing"

	"apps/backend/internal/module/radar/domain"
	"apps/backend/internal/module/radar/repository"
)

func newTestService() *Service {
	return New(repository.NewMemory())
}

func sampleProfile() *domain.ServiceProfile {
	return &domain.ServiceProfile{
		Services: []domain.ServiceItem{
			{Name: "婚禮攝影", PriceMin: 18000, PriceMax: 36000},
			{Name: "活動紀錄", PriceMin: 8000},
		},
		Cases:        []domain.ServiceCase{{Title: "陽明山戶外婚禮", Summary: "全天紀錄", Link: "https://example.com/case/1"}},
		Forbidden:    []string{"保證接到案", " 保證接到案 ", "最便宜"},
		Faq:          []domain.FaqItem{{Question: "可以加時嗎？", Answer: "可以，每小時 3000。"}},
		ServiceAreas: []string{"TPE", "nwt", "TPE"},
		Availability: "平日全天、週末僅早場",
		ToneNote:     "親切、不推銷",
	}
}

func TestUpsertThenGetKeepsEveryField(t *testing.T) {
	svc := newTestService()
	ctx := context.Background()

	saved, err := svc.UpsertServiceProfile(ctx, 42, sampleProfile())
	if err != nil {
		t.Fatalf("upsert: %v", err)
	}

	got, err := svc.GetServiceProfile(ctx, 42)
	if err != nil {
		t.Fatalf("get: %v", err)
	}
	if got.OwnerUID != 42 {
		t.Fatalf("owner_uid = %d, want 42", got.OwnerUID)
	}
	if len(got.Services) != 2 || got.Services[0].Name != "婚禮攝影" {
		t.Fatalf("services not round-tripped: %+v", got.Services)
	}
	// 只填 price_min 是合法的「起價」，幣別要自動補上才顯示得出來。
	if got.Services[1].Currency != "TWD" {
		t.Fatalf("currency = %q, want TWD", got.Services[1].Currency)
	}
	if len(got.Cases) != 1 || got.Cases[0].Title != "陽明山戶外婚禮" {
		t.Fatalf("cases not round-tripped: %+v", got.Cases)
	}
	// forbidden 是回覆生成的硬性過濾詞，讀不到就等於過濾失效。
	if len(got.Forbidden) != 2 {
		t.Fatalf("forbidden = %v, want 2 deduped items", got.Forbidden)
	}
	if len(got.Faq) != 1 || got.Faq[0].Answer == "" {
		t.Fatalf("faq not round-tripped: %+v", got.Faq)
	}
	if len(got.ServiceAreas) != 2 || got.ServiceAreas[0] != "TPE" || got.ServiceAreas[1] != "NWT" {
		t.Fatalf("service_areas = %v, want [TPE NWT]", got.ServiceAreas)
	}
	if got.Availability == "" || got.ToneNote == "" {
		t.Fatalf("availability/tone_note lost: %+v", got)
	}
	if got.UpdatedAt <= 0 || got.CreatedAt <= 0 {
		t.Fatalf("timestamps unset: created=%d updated=%d", got.CreatedAt, got.UpdatedAt)
	}
	if saved.UpdatedAt != got.UpdatedAt {
		t.Fatalf("upsert returned a different revision than get")
	}
}

func TestUpsertRejectsReversedPriceRange(t *testing.T) {
	svc := newTestService()
	p := sampleProfile()
	p.Services[0].PriceMin = 50000
	p.Services[0].PriceMax = 10000

	_, err := svc.UpsertServiceProfile(context.Background(), 42, p)
	if !errors.Is(err, domain.ErrValidation) {
		t.Fatalf("err = %v, want ErrValidation", err)
	}
}

func TestUpsertRejectsUnknownServiceAreaCode(t *testing.T) {
	svc := newTestService()
	p := sampleProfile()
	// 縣市只吃代碼白名單；接受「台北」會讓判定的地區比對變成模糊猜測。
	p.ServiceAreas = []string{"台北"}

	_, err := svc.UpsertServiceProfile(context.Background(), 42, p)
	if !errors.Is(err, domain.ErrValidation) {
		t.Fatalf("err = %v, want ErrValidation", err)
	}
}

func TestUpsertRequiresAreasUnlessRemote(t *testing.T) {
	svc := newTestService()
	ctx := context.Background()

	p := sampleProfile()
	p.ServiceAreas = nil
	if _, err := svc.UpsertServiceProfile(ctx, 42, p); !errors.Is(err, domain.ErrValidation) {
		t.Fatalf("err = %v, want ErrValidation for no area and no remote", err)
	}

	p = sampleProfile()
	p.ServiceAreas = nil
	p.RemoteOk = true
	if _, err := svc.UpsertServiceProfile(ctx, 42, p); err != nil {
		t.Fatalf("remote-only profile rejected: %v", err)
	}
}

func TestUpsertRequiresAtLeastOneService(t *testing.T) {
	svc := newTestService()
	p := sampleProfile()
	p.Services = nil

	_, err := svc.UpsertServiceProfile(context.Background(), 42, p)
	if !errors.Is(err, domain.ErrValidation) {
		t.Fatalf("err = %v, want ErrValidation", err)
	}
}

// 「沒建檔」與「建了一份空的」必須分得出來：列表的 profile_exists 靠這個差別。
func TestGetReportsNotFoundBeforeFirstUpsert(t *testing.T) {
	svc := newTestService()
	ctx := context.Background()

	_, err := svc.GetServiceProfile(ctx, 42)
	if !errors.Is(err, domain.ErrNotFound) {
		t.Fatalf("err = %v, want ErrNotFound", err)
	}
	has, err := svc.HasServiceProfile(ctx, 42)
	if err != nil || has {
		t.Fatalf("HasServiceProfile = (%v, %v), want (false, nil)", has, err)
	}

	if _, err := svc.UpsertServiceProfile(ctx, 42, sampleProfile()); err != nil {
		t.Fatalf("upsert: %v", err)
	}
	has, err = svc.HasServiceProfile(ctx, 42)
	if err != nil || !has {
		t.Fatalf("HasServiceProfile = (%v, %v), want (true, nil)", has, err)
	}
}

func TestUpsertIsWholeDocumentReplacementAndKeepsCreatedAt(t *testing.T) {
	svc := newTestService()
	ctx := context.Background()

	first, err := svc.UpsertServiceProfile(ctx, 42, sampleProfile())
	if err != nil {
		t.Fatalf("upsert: %v", err)
	}

	trimmed := sampleProfile()
	trimmed.Services = trimmed.Services[:1]
	trimmed.Forbidden = nil
	second, err := svc.UpsertServiceProfile(ctx, 42, trimmed)
	if err != nil {
		t.Fatalf("second upsert: %v", err)
	}
	// 整份覆寫：刪掉的項目要真的消失，否則使用者永遠刪不掉一個服務或禁語。
	if len(second.Services) != 1 {
		t.Fatalf("services = %d, want 1 after replacement", len(second.Services))
	}
	if len(second.Forbidden) != 0 {
		t.Fatalf("forbidden = %v, want empty after replacement", second.Forbidden)
	}
	if second.CreatedAt != first.CreatedAt {
		t.Fatalf("created_at changed on update: %d → %d", first.CreatedAt, second.CreatedAt)
	}
}

func TestProfilesAreIsolatedPerOwner(t *testing.T) {
	svc := newTestService()
	ctx := context.Background()

	if _, err := svc.UpsertServiceProfile(ctx, 42, sampleProfile()); err != nil {
		t.Fatalf("upsert: %v", err)
	}
	if _, err := svc.GetServiceProfile(ctx, 43); !errors.Is(err, domain.ErrNotFound) {
		t.Fatalf("owner 43 saw owner 42's profile: %v", err)
	}
}

func TestMatchesRegion(t *testing.T) {
	p := &domain.ServiceProfile{ServiceAreas: []string{"TPE"}}
	if !p.MatchesRegion("tpe") {
		t.Fatal("TPE should match regardless of case")
	}
	if p.MatchesRegion("KHH") {
		t.Fatal("KHH must not match a Taipei-only profile")
	}
	// 判不出縣市時不算符合，但也不該被當成不符合 —— 那是呼叫端的 unknown 分支。
	if p.MatchesRegion("") {
		t.Fatal("empty region must not count as a match")
	}
	remote := &domain.ServiceProfile{RemoteOk: true}
	if !remote.MatchesRegion("") {
		t.Fatal("remote_ok profile serves any region")
	}
}
