package runner

import (
	"context"
	"fmt"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/codes"

	"github.com/libnovel/backend/internal/domain"
)

// runTranslationTask executes one machine-translation task end-to-end and
// reports the result back to PocketBase.
func (r *Runner) runTranslationTask(ctx context.Context, task domain.TranslationTask) {
	ctx, span := otel.Tracer("runner").Start(ctx, "runner.translation_task")
	defer span.End()
	span.SetAttributes(
		attribute.String("task.id", task.ID),
		attribute.String("book.slug", task.Slug),
		attribute.Int("chapter.number", task.Chapter),
		attribute.String("translation.lang", task.Lang),
	)

	log := r.deps.Log.With("task_id", task.ID, "slug", task.Slug, "chapter", task.Chapter, "lang", task.Lang)
	log.Info("runner: translation task starting")

	// Heartbeat goroutine — keeps the task alive while translation runs.
	hbCtx, hbCancel := context.WithCancel(ctx)
	defer hbCancel()
	go func() {
		tick := time.NewTicker(r.cfg.HeartbeatInterval)
		defer tick.Stop()
		for {
			select {
			case <-hbCtx.Done():
				return
			case <-tick.C:
				if err := r.deps.Consumer.HeartbeatTask(ctx, task.ID); err != nil {
					log.Warn("runner: heartbeat failed", "err", err)
				}
			}
		}
	}()

	fail := func(msg string) {
		log.Error("runner: translation task failed", "reason", msg)
		r.tasksFailed.Add(1)
		span.SetStatus(codes.Error, msg)
		result := domain.TranslationResult{ErrorMessage: msg}
		if err := r.deps.Consumer.FinishTranslationTask(ctx, task.ID, result); err != nil {
			log.Error("runner: FinishTranslationTask failed", "err", err)
		}
		if r.deps.Notifier != nil {
			_ = r.deps.Notifier.CreateNotification(ctx, "admin",
				"Translation Failed",
				fmt.Sprintf("Ch.%d of %s (%s): %s", task.Chapter, task.Slug, task.Lang, msg),
				fmt.Sprintf("/books/%s", task.Slug))
		}
	}

	// Guard: LibreTranslate must be configured.
	if r.deps.LibreTranslate == nil {
		fail("libretranslate client not configured (LIBRETRANSLATE_URL is empty)")
		return
	}

	// 1. Read raw markdown chapter.
	raw, err := r.deps.BookReader.ReadChapter(ctx, task.Slug, task.Chapter)
	if err != nil {
		fail(fmt.Sprintf("read chapter: %v", err))
		return
	}
	if raw == "" {
		fail("chapter text is empty")
		return
	}

	// 2. Translate (chunked, concurrent).
	translated, err := r.deps.LibreTranslate.Translate(ctx, raw, "en", task.Lang)
	if err != nil {
		fail(fmt.Sprintf("translate: %v", err))
		return
	}

	// 3. Store translated markdown in MinIO.
	key := r.deps.TranslationStore.TranslationObjectKey(task.Lang, task.Slug, task.Chapter)
	if err := r.deps.TranslationStore.PutTranslation(ctx, key, []byte(translated)); err != nil {
		fail(fmt.Sprintf("put translation: %v", err))
		return
	}

	// 4. Report success.
	r.tasksCompleted.Add(1)
	span.SetStatus(codes.Ok, "")
	result := domain.TranslationResult{ObjectKey: key}
	if err := r.deps.Consumer.FinishTranslationTask(ctx, task.ID, result); err != nil {
		log.Error("runner: FinishTranslationTask failed", "err", err)
	}
	if r.deps.Notifier != nil {
		_ = r.deps.Notifier.CreateNotification(ctx, "admin",
			"Translation Ready",
			fmt.Sprintf("Ch.%d of %s translated to %s", task.Chapter, task.Slug, task.Lang),
			fmt.Sprintf("/books/%s", task.Slug))
	}
	log.Info("runner: translation task finished", "key", key)
}
