package usecase

import (
	"context"
	"fmt"
	"strings"

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

type WatchInput struct {
	Terms        []string
	ExcludeTerms []string
	Regions      []string
	BrandID      string
	ProductID    string
	// Enabled=false 代表建立成 paused，可先備好關鍵字再開。
	Enabled bool
}

type WatchPatch struct {
	Terms        *[]string
	ExcludeTerms *[]string
	Regions      *[]string
}

func (s *Service) CreateWatch(ctx context.Context, ownerUID int64, in WatchInput) (*domain.RadarWatch, error) {
	if ownerUID <= 0 {
		return nil, fmt.Errorf("%w: owner_uid required", domain.ErrValidation)
	}
	status := domain.WatchPaused
	if in.Enabled {
		status = domain.WatchActive
	}
	now := domain.NowNano()
	w := &domain.RadarWatch{
		ID:           domain.NewID(),
		OwnerUID:     ownerUID,
		Terms:        in.Terms,
		ExcludeTerms: in.ExcludeTerms,
		Regions:      in.Regions,
		Status:       status,
		CreatedAt:    now,
		UpdatedAt:    now,
	}
	brandID, productID := strings.TrimSpace(in.BrandID), strings.TrimSpace(in.ProductID)
	if (brandID == "") != (productID == "") {
		return nil, fmt.Errorf("%w: brand_id and product_id must be provided together", domain.ErrValidation)
	}
	productWatch := brandID != ""
	if productWatch {
		product, err := s.LoadProductContext(ctx, ownerUID, brandID, productID)
		if err != nil {
			return nil, err
		}
		w.ContextMode, w.BrandID, w.ProductID = domain.WatchContextProduct, product.BrandID, product.ProductID
		w.BrandNameSnapshot, w.ProductLabelSnapshot, w.ContextBoundAt = product.BrandName, product.ProductLabel, now
	}
	if err := w.Normalize(); err != nil {
		return nil, err
	}
	firstEverWatch := false
	if status == domain.WatchActive {
		if err := s.assertCanActivateForWatch(ctx, ownerUID, "", productWatch); err != nil {
			return nil, err
		}
		// 只在使用者從未建過任何訂閱時才判定「首巡」，避免每次新增都額外燒一次巡檢成本。
		if _, total, err := s.Repo.ListWatches(ctx, ownerUID, domain.WatchListFilter{Page: 1, PageSize: 1}); err == nil {
			firstEverWatch = total == 0
		}
	}
	if err := s.Repo.SaveWatch(ctx, w); err != nil {
		return nil, err
	}
	if firstEverWatch && s.SweepJobs != nil {
		// 不等每日排程：讓第一組訂閱的使用者最快隔一小段時間就看到結果，而不是等到隔天。
		// 失敗不阻斷建立，每日排程仍會補上。
		if _, sweepErr := s.SweepJobs.ScheduleRadarSweep(ctx, ownerUID, w.ID, domain.NowNano()); sweepErr == nil {
			w.FirstSweepTriggered = true
		}
	}
	return w, nil
}

// GetWatch 以 owner 檢查取代單純 id 查詢：id 是 uuid，但不該靠不可預測性當授權。
func (s *Service) GetWatch(ctx context.Context, ownerUID int64, id string) (*domain.RadarWatch, error) {
	w, err := s.Repo.GetWatch(ctx, id)
	if err != nil {
		return nil, err
	}
	if w.OwnerUID != ownerUID {
		return nil, domain.ErrForbidden
	}
	return w, nil
}

func (s *Service) ListWatches(ctx context.Context, ownerUID int64, f domain.WatchListFilter) ([]*domain.RadarWatch, int64, error) {
	if ownerUID <= 0 {
		return nil, 0, fmt.Errorf("%w: owner_uid required", domain.ErrValidation)
	}
	if f.Status != "" && !domain.IsWatchStatus(f.Status) {
		return nil, 0, fmt.Errorf("%w: unknown status filter %q", domain.ErrValidation, f.Status)
	}
	if f.PageSize > 50 {
		f.PageSize = 50
	}
	return s.Repo.ListWatches(ctx, ownerUID, f)
}

func (s *Service) ListActiveWatches(ctx context.Context, ownerUID int64) ([]*domain.RadarWatch, error) {
	return s.Repo.ListActiveWatches(ctx, ownerUID)
}

func (s *Service) CountActiveWatches(ctx context.Context, ownerUID int64) (int64, error) {
	return s.Repo.CountActiveWatches(ctx, ownerUID)
}

/*
UpdateWatch 只改關鍵字與地區；狀態一律走 Pause／Resume／Archive。

nil 欄位代表不動，這樣「只改地區」不會意外清空關鍵字。
*/
func (s *Service) UpdateWatch(ctx context.Context, ownerUID int64, id string, patch WatchPatch) (*domain.RadarWatch, error) {
	w, err := s.GetWatch(ctx, ownerUID, id)
	if err != nil {
		return nil, err
	}
	if w.Status == domain.WatchArchived {
		return nil, fmt.Errorf("%w: archived watch cannot be edited", domain.ErrValidation)
	}
	if patch.Terms != nil {
		w.Terms = *patch.Terms
	}
	if patch.ExcludeTerms != nil {
		w.ExcludeTerms = *patch.ExcludeTerms
	}
	if patch.Regions != nil {
		w.Regions = *patch.Regions
	}
	if err := w.Normalize(); err != nil {
		return nil, err
	}
	w.UpdatedAt = domain.NowNano()
	if err := s.Repo.SaveWatch(ctx, w); err != nil {
		return nil, err
	}
	return w, nil
}

func (s *Service) PauseWatch(ctx context.Context, ownerUID int64, id string) (*domain.RadarWatch, error) {
	return s.transition(ctx, ownerUID, id, domain.WatchPaused)
}

/*
ResumeWatch 回到 active，因此要再過一次配額閘：暫停期間方案可能已降級，
不重驗就會讓人靠「暫停再恢復」繞過上限。
*/
func (s *Service) ResumeWatch(ctx context.Context, ownerUID int64, id string) (*domain.RadarWatch, error) {
	w, err := s.GetWatch(ctx, ownerUID, id)
	if err != nil {
		return nil, err
	}
	if w.Status != domain.WatchActive {
		if err := s.assertCanActivateForWatch(ctx, ownerUID, w.ID, w.ContextMode == domain.WatchContextProduct); err != nil {
			return nil, err
		}
		// An unavailable-product pause is normally terminal, but a transient
		// catalog/worker wiring outage must not strand a valid watch forever.
		// Revalidate the current owned Brand/Product before clearing the guard;
		// a genuinely deleted or mismatched product still cannot resume.
		if w.PauseReason == domain.PauseReasonProductUnavailable || w.PauseReason == domain.PauseReasonBrandUnavailable {
			if w.ContextMode != domain.WatchContextProduct {
				return nil, fmt.Errorf("%w: unavailable watch has no product context", domain.ErrValidation)
			}
			if _, err := s.LoadProductContext(ctx, ownerUID, w.BrandID, w.ProductID); err != nil {
				return nil, err
			}
			w.PauseReason = ""
		}
	}
	return s.applyTransition(ctx, w, domain.WatchActive)
}

// AssignWatchProduct is the one-way migration from a generic watch to an
// owned Brand/Product context. An already-bound product cannot be replaced.
func (s *Service) AssignWatchProduct(ctx context.Context, ownerUID int64, id, brandID, productID string) (*domain.RadarWatch, error) {
	w, err := s.GetWatch(ctx, ownerUID, id)
	if err != nil {
		return nil, err
	}
	brandID, productID = strings.TrimSpace(brandID), strings.TrimSpace(productID)
	if brandID == "" || productID == "" {
		return nil, fmt.Errorf("%w: brand_id and product_id required", domain.ErrValidation)
	}
	product, err := s.LoadProductContext(ctx, ownerUID, brandID, productID)
	if err != nil {
		return nil, err
	}
	if err := w.BindProduct(product.BrandID, product.ProductID, product.BrandName, product.ProductLabel, domain.NowNano()); err != nil {
		return nil, err
	}
	if err := s.Repo.SaveWatch(ctx, w); err != nil {
		return nil, err
	}
	return w, nil
}

// ArchiveWatch 是軟刪：歷史商機與統計都留著。
func (s *Service) ArchiveWatch(ctx context.Context, ownerUID int64, id string) error {
	_, err := s.transition(ctx, ownerUID, id, domain.WatchArchived)
	return err
}

// DeleteArchivedWatch permanently removes only the archived watch definition.
// Sweeps, opportunities, and replies are deliberately retained as history;
// deleting a watch must never erase business records produced by it.
func (s *Service) DeleteArchivedWatch(ctx context.Context, ownerUID int64, id string) error {
	w, err := s.GetWatch(ctx, ownerUID, id)
	if err != nil {
		return err
	}
	if w.Status != domain.WatchArchived {
		return fmt.Errorf("%w: only archived watch can be permanently deleted", domain.ErrValidation)
	}
	return s.Repo.DeleteWatch(ctx, id)
}

func (s *Service) MarkWatchSwept(ctx context.Context, id string, at int64) error {
	if at <= 0 {
		at = domain.NowNano()
	}
	return s.Repo.TouchWatchSweptAt(ctx, id, at)
}

func (s *Service) transition(ctx context.Context, ownerUID int64, id, to string) (*domain.RadarWatch, error) {
	w, err := s.GetWatch(ctx, ownerUID, id)
	if err != nil {
		return nil, err
	}
	return s.applyTransition(ctx, w, to)
}

func (s *Service) applyTransition(ctx context.Context, w *domain.RadarWatch, to string) (*domain.RadarWatch, error) {
	if err := w.Transition(to); err != nil {
		return nil, err
	}
	if err := s.Repo.SaveWatch(ctx, w); err != nil {
		return nil, err
	}
	return w, nil
}
