package usecase

import (
	"context"
	"strings"

	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	"haixun-backend/internal/model/persona/domain/entity"
	domrepo "haixun-backend/internal/model/persona/domain/repository"
	domusecase "haixun-backend/internal/model/persona/domain/usecase"

	"github.com/google/uuid"
)

type personaUseCase struct {
	repo domrepo.Repository
}

func NewUseCase(repo domrepo.Repository) domusecase.UseCase {
	return &personaUseCase{repo: repo}
}

func (u *personaUseCase) List(ctx context.Context, tenantID, ownerUID string) (*domusecase.ListResult, error) {
	if err := requireActor(tenantID, ownerUID); err != nil {
		return nil, err
	}
	items, err := u.repo.ListByOwner(ctx, tenantID, ownerUID)
	if err != nil {
		return nil, err
	}
	list := make([]domusecase.PersonaSummary, 0, len(items))
	for _, item := range items {
		list = append(list, toSummary(item))
	}
	return &domusecase.ListResult{List: list}, nil
}

func (u *personaUseCase) Create(ctx context.Context, req domusecase.CreateRequest) (*domusecase.PersonaSummary, error) {
	if err := requireActor(req.TenantID, req.OwnerUID); err != nil {
		return nil, err
	}
	displayName := strings.TrimSpace(req.DisplayName)
	if displayName == "" {
		existing, err := u.repo.ListByOwner(ctx, req.TenantID, req.OwnerUID)
		if err != nil {
			return nil, err
		}
		displayName = "人設 " + itoa(len(existing)+1)
	}
	item, err := u.repo.Create(ctx, &entity.Persona{
		ID:          uuid.NewString(),
		TenantID:    req.TenantID,
		OwnerUID:    req.OwnerUID,
		DisplayName: displayName,
		Status:      entity.StatusOpen,
	})
	if err != nil {
		return nil, err
	}
	summary := toSummary(item)
	return &summary, nil
}

func (u *personaUseCase) Get(ctx context.Context, tenantID, ownerUID, personaID string) (*domusecase.PersonaSummary, error) {
	item, err := u.assertOwned(ctx, tenantID, ownerUID, personaID)
	if err != nil {
		return nil, err
	}
	summary := toSummary(item)
	return &summary, nil
}

func (u *personaUseCase) Delete(ctx context.Context, tenantID, ownerUID, personaID string) error {
	if _, err := u.assertOwned(ctx, tenantID, ownerUID, personaID); err != nil {
		return err
	}
	return u.repo.SoftDelete(ctx, tenantID, ownerUID, personaID)
}

func (u *personaUseCase) Update(ctx context.Context, req domusecase.UpdateRequest) (*domusecase.PersonaSummary, error) {
	if _, err := u.assertOwned(ctx, req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
		return nil, err
	}
	patch := patchToMap(req.Patch)
	item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.PersonaID, patch)
	if err != nil {
		return nil, err
	}
	summary := toSummary(item)
	return &summary, nil
}

func (u *personaUseCase) assertOwned(ctx context.Context, tenantID, ownerUID, personaID string) (*entity.Persona, error) {
	if err := requireActor(tenantID, ownerUID); err != nil {
		return nil, err
	}
	if strings.TrimSpace(personaID) == "" {
		return nil, app.For(code.Persona).InputMissingRequired("persona id is required")
	}
	return u.repo.FindByID(ctx, tenantID, ownerUID, personaID)
}

func requireActor(tenantID, ownerUID string) error {
	if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" {
		return app.For(code.Persona).InputMissingRequired("tenant_id and uid are required")
	}
	return nil
}

func toSummary(item *entity.Persona) domusecase.PersonaSummary {
	if item == nil {
		return domusecase.PersonaSummary{}
	}
	return domusecase.PersonaSummary{
		ID:              item.ID,
		DisplayName:     item.DisplayName,
		Persona:         item.Persona,
		Brief:           item.Brief,
		ProductBrief:    item.ProductBrief,
		TargetAudience:  item.TargetAudience,
		Goals:           item.Goals,
		StyleProfile:    item.StyleProfile,
		StyleBenchmark:  item.StyleBenchmark,
		SeedQuery:       item.SeedQuery,
		CopyResearchMap: toCopyMapSummary(item.CopyResearchMap),
		CreateAt:        item.CreateAt,
		UpdateAt:        item.UpdateAt,
	}
}

func toCopyMapSummary(m entity.CopyResearchMap) domusecase.CopyResearchMapSummary {
	return domusecase.CopyResearchMapSummary{
		AudienceSummary: m.AudienceSummary,
		ContentGoal:     m.ContentGoal,
		Questions:       append([]string(nil), m.Questions...),
		Pillars:         append([]string(nil), m.Pillars...),
		Exclusions:      append([]string(nil), m.Exclusions...),
		SuggestedTags:   append([]string(nil), m.SuggestedTags...),
		BenchmarkNotes:  m.BenchmarkNotes,
	}
}

func patchToMap(patch domusecase.PersonaPatch) map[string]interface{} {
	out := map[string]interface{}{}
	if patch.DisplayName != nil {
		out["display_name"] = strings.TrimSpace(*patch.DisplayName)
	}
	if patch.Persona != nil {
		out["persona"] = strings.TrimSpace(*patch.Persona)
	}
	if patch.Brief != nil {
		out["brief"] = strings.TrimSpace(*patch.Brief)
	}
	if patch.ProductBrief != nil {
		out["product_brief"] = strings.TrimSpace(*patch.ProductBrief)
	}
	if patch.TargetAudience != nil {
		out["target_audience"] = strings.TrimSpace(*patch.TargetAudience)
	}
	if patch.Goals != nil {
		out["goals"] = strings.TrimSpace(*patch.Goals)
	}
	if patch.StyleProfile != nil {
		out["style_profile"] = strings.TrimSpace(*patch.StyleProfile)
	}
	if patch.StyleBenchmark != nil {
		out["style_benchmark"] = strings.TrimSpace(*patch.StyleBenchmark)
	}
	if patch.SeedQuery != nil {
		out["seed_query"] = strings.TrimSpace(*patch.SeedQuery)
	}
	if patch.CopyResearchMap != nil {
		out["copy_research_map"] = *patch.CopyResearchMap
	}
	return out
}

func itoa(n int) string {
	if n <= 0 {
		return "1"
	}
	buf := make([]byte, 0, 12)
	for n > 0 {
		buf = append(buf, byte('0'+n%10))
		n /= 10
	}
	for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 {
		buf[i], buf[j] = buf[j], buf[i]
	}
	return string(buf)
}
