package usecase

import (
	"context"
	"fmt"
	"strings"

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

func (s *Service) GetOpportunity(ctx context.Context, ownerUID int64, id string) (*domain.Opportunity, error) {
	o, err := s.Repo.GetOpportunity(ctx, id)
	if err != nil {
		return nil, err
	}
	if o.OwnerUID != ownerUID {
		return nil, domain.ErrForbidden
	}
	return o, nil
}

func (s *Service) ListOpportunities(ctx context.Context, ownerUID int64, f domain.OpportunityListFilter) ([]*domain.Opportunity, int64, error) {
	if ownerUID <= 0 {
		return nil, 0, fmt.Errorf("%w: owner_uid required", domain.ErrValidation)
	}
	if f.FitBand != "" && !domain.IsProductFitBand(f.FitBand) {
		return nil, 0, fmt.Errorf("%w: unknown product fit band %q", domain.ErrValidation, f.FitBand)
	}
	if f.MatchState != "" && !strings.Contains(" eligible weak excluded generic stale ", " "+f.MatchState+" ") {
		return nil, 0, fmt.Errorf("%w: unknown product match state %q", domain.ErrValidation, f.MatchState)
	}
	if f.Sort != "" && f.Sort != "posted" && f.Sort != "score" && f.Sort != "recommended" && f.Sort != "newest" && f.Sort != "oldest" && f.Sort != "product_fit" && f.Sort != "demand_intent" && f.Sort != "priority" && f.Sort != "priority_score" {
		return nil, 0, fmt.Errorf("%w: unknown opportunity sort %q", domain.ErrValidation, f.Sort)
	}
	if f.ReviewState != "" && !domain.IsReviewState(f.ReviewState) {
		return nil, 0, fmt.Errorf("%w: unknown review state %q", domain.ErrValidation, f.ReviewState)
	}
	if f.TimeScope != "" && f.TimeScope != "today" && f.TimeScope != "7d" && f.TimeScope != "all" {
		return nil, 0, fmt.Errorf("%w: unknown time scope %q", domain.ErrValidation, f.TimeScope)
	}
	if f.PriorityBand != "" && f.PriorityBand != "high" && f.PriorityBand != "review" && f.PriorityBand != "low" {
		return nil, 0, fmt.Errorf("%w: unknown priority band %q", domain.ErrValidation, f.PriorityBand)
	}
	return s.Repo.ListOpportunities(ctx, ownerUID, f)
}

func (s *Service) UpdateOpportunityReviewState(ctx context.Context, ownerUID int64, id string, patch domain.ReviewStatePatch) (*domain.Opportunity, error) {
	if ownerUID <= 0 || strings.TrimSpace(id) == "" {
		return nil, fmt.Errorf("%w: owner and opportunity are required", domain.ErrValidation)
	}
	if patch.State == domain.ReviewRemoved && patch.RemovalReason == "" {
		return nil, fmt.Errorf("%w: removal reason required", domain.ErrValidation)
	}
	return s.Repo.UpdateOpportunityReviewState(ctx, ownerUID, id, patch)
}

func (s *Service) ListSweeps(ctx context.Context, ownerUID int64, f domain.SweepListFilter) ([]*domain.RadarSweep, int64, error) {
	return s.Repo.ListSweeps(ctx, ownerUID, f)
}

// TriggerSweep enqueues a radar_sweep job for a watch (manual). Same path as daily schedule.
func (s *Service) TriggerSweep(ctx context.Context, ownerUID int64, watchID string) (jobID string, err error) {
	if s.SweepJobs == nil {
		return "", fmt.Errorf("%w: sweep scheduler not configured", domain.ErrNotReady)
	}
	w, err := s.Repo.GetWatch(ctx, watchID)
	if err != nil {
		return "", err
	}
	if w.OwnerUID != ownerUID {
		return "", domain.ErrForbidden
	}
	if w.Status != domain.WatchActive {
		return "", fmt.Errorf("%w: only active watches can be swept (status=%s)", domain.ErrValidation, w.Status)
	}
	// Manual trigger is due now and must not reuse a finished daily slot.
	if manual, ok := s.SweepJobs.(ManualSweepJobScheduler); ok {
		return manual.ScheduleManualRadarSweep(ctx, ownerUID, watchID, domain.NowNano())
	}
	return s.SweepJobs.ScheduleRadarSweep(ctx, ownerUID, watchID, domain.NowNano())
}

func (s *Service) AcceptOpportunity(ctx context.Context, ownerUID int64, id string) (*domain.Opportunity, string, error) {
	o, err := s.GetOpportunity(ctx, ownerUID, id)
	if err != nil {
		return nil, "", err
	}
	if err := o.Transition(domain.OppAccepted); err != nil {
		return nil, "", err
	}
	contactID := o.ContactID
	if s.CRM != nil {
		cid, berr := s.CRM.BindOpportunity(ctx, ownerUID, o)
		if berr != nil {
			return nil, "", berr
		}
		contactID = cid
		o.ContactID = cid
	}
	if err := s.Repo.UpdateOpportunityStatus(ctx, id, domain.OppAccepted); err != nil {
		return nil, "", err
	}
	if contactID != "" {
		if err := s.setContactID(ctx, id, contactID); err != nil {
			return nil, "", err
		}
	}
	// 加入 CRM 是收件匣的正向完成動作。業務狀態 accepted 與工作狀態
	// completed 必須一起成立，否則加入名單後同一張卡仍會留在待處理。
	o, err = s.Repo.UpdateOpportunityReviewState(ctx, ownerUID, id, domain.ReviewStatePatch{State: domain.ReviewCompleted})
	if err != nil {
		return nil, contactID, err
	}
	return o, contactID, nil
}

func (s *Service) setContactID(ctx context.Context, id, contactID string) error {
	type patcher interface {
		SetOpportunityContact(ctx context.Context, id, contactID string) error
	}
	if p, ok := s.Repo.(patcher); ok {
		return p.SetOpportunityContact(ctx, id, contactID)
	}
	return nil
}

func (s *Service) DismissOpportunity(ctx context.Context, ownerUID int64, id, reason string) (*domain.Opportunity, error) {
	o, err := s.GetOpportunity(ctx, ownerUID, id)
	if err != nil {
		return nil, err
	}
	if err := o.Transition(domain.OppDismissed); err != nil {
		return nil, err
	}
	if err := s.Repo.UpdateOpportunityStatus(ctx, id, domain.OppDismissed); err != nil {
		return nil, err
	}
	_ = reason
	return s.Repo.GetOpportunity(ctx, id)
}

func (s *Service) OverrideOpportunity(ctx context.Context, ownerUID int64, id, band, status string) (*domain.Opportunity, error) {
	o, err := s.GetOpportunity(ctx, ownerUID, id)
	if err != nil {
		return nil, err
	}
	ov := &domain.OpportunityOverride{
		FromBand:   o.IntentBand,
		ToBand:     band,
		FromStatus: o.Status,
		ToStatus:   status,
		ActorUID:   ownerUID,
		At:         domain.NowNano(),
	}
	if status == "" {
		status = o.Status
	}
	if band == "" {
		band = o.IntentBand
	}
	if err := s.Repo.SetOpportunityOverride(ctx, id, ov, band, status); err != nil {
		return nil, err
	}
	return s.Repo.GetOpportunity(ctx, id)
}
