package batch

import (
	"context"
	"fmt"
	"strings"

	"sub2api-cn-relay-manager/internal/host/sub2api"
	"sub2api-cn-relay-manager/internal/store/sqlite"
)

type ValidationItemStore interface {
	Upsert(ctx context.Context, item sqlite.ImportRunItem) error
}

type ValidationRunStore interface {
	GetByRunID(ctx context.Context, runID string) (sqlite.ImportRun, error)
	Update(ctx context.Context, run sqlite.ImportRun) error
}

type ValidationService struct {
	ItemStore ValidationItemStore
	RunStore  ValidationRunStore
	Validator func(ctx context.Context, item sqlite.ImportRunItem) (sub2api.GatewayCompletionResult, error)
}

func (s ValidationService) ValidateItem(ctx context.Context, item sqlite.ImportRunItem) error {
	if s.ItemStore == nil {
		return fmt.Errorf("item store is required")
	}
	if s.RunStore == nil {
		return fmt.Errorf("run store is required")
	}
	if s.Validator == nil {
		return fmt.Errorf("validator is required")
	}
	if strings.TrimSpace(item.CurrentStage) != string(ItemStageValidate) {
		return fmt.Errorf("item %s is not ready for validation", strings.TrimSpace(item.ItemID))
	}

	completion, err := s.Validator(ctx, item)
	if err != nil {
		return err
	}

	item.CurrentStage = string(ItemStageDone)
	item.AccessStatus = string(resolveValidationAccessStatus(item.ConfirmationStatus, completion))
	if item.AccessStatus == string(AccessStatusDegraded) {
		item.AdvisoryMessagesJSON = appendAdvisoryJSON(item.AdvisoryMessagesJSON, "gateway_warmup_retry_succeeded")
	}
	if !completion.OK {
		item.LastErrorStage = string(ItemStageValidate)
		item.LastError = strings.TrimSpace(completion.BodyPreview)
	}

	if err := s.ItemStore.Upsert(ctx, item); err != nil {
		return err
	}

	run, err := s.RunStore.GetByRunID(ctx, item.RunID)
	if err != nil {
		return err
	}

	run.CompletedItems++
	switch item.AccessStatus {
	case string(AccessStatusActive):
		run.ActiveItems++
	case string(AccessStatusDegraded):
		run.DegradedItems++
		run.WarningItems++
	case string(AccessStatusBroken):
		run.BrokenItems++
	}
	run.State = deriveRunState(run)

	return s.RunStore.Update(ctx, run)
}

func resolveValidationAccessStatus(confirmationStatus string, completion sub2api.GatewayCompletionResult) AccessStatus {
	switch strings.TrimSpace(confirmationStatus) {
	case string(ConfirmationFailed):
		return AccessStatusBroken
	case string(ConfirmationConfirmed), string(ConfirmationAdvisory):
		if completion.OK && completion.StatusCode >= 200 && completion.StatusCode < 300 {
			return AccessStatusActive
		}
		if isTransientValidationFailure(completion) {
			return AccessStatusDegraded
		}
		return AccessStatusBroken
	default:
		return AccessStatusBroken
	}
}

func isTransientValidationFailure(result sub2api.GatewayCompletionResult) bool {
	if result.OK {
		return false
	}
	if result.StatusCode != 0 && result.StatusCode != 429 && result.StatusCode != 502 && result.StatusCode != 503 && result.StatusCode != 504 {
		return false
	}

	body := strings.ToLower(strings.TrimSpace(result.BodyPreview))
	return strings.Contains(body, "service temporarily unavailable") ||
		strings.Contains(body, "no available accounts") ||
		strings.Contains(body, "temporar") ||
		strings.Contains(body, "try again")
}

func deriveRunState(run sqlite.ImportRun) string {
	if run.TotalItems > 0 && run.CompletedItems >= run.TotalItems {
		switch {
		case run.BrokenItems > 0:
			return string(RunStateFailed)
		case run.WarningItems > 0 || run.DegradedItems > 0:
			return string(RunStateCompletedWithWarnings)
		default:
			return string(RunStateCompleted)
		}
	}
	return firstNonEmptyRunState(run.State, string(RunStateRunning))
}

func firstNonEmptyRunState(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return string(RunStateRunning)
}
