package backend

import (
	"context"
	"errors"
	"fmt"
	"math"
	"strings"
)

// extractAudioSegmentsWithTags is shared by standalone audio extraction and
// video audio-track extraction so transcript, tag, and checkpoint progress
// cannot diverge between the two workers.
func (b *Dat9Backend) extractAudioSegmentsWithTags(
	ctx context.Context,
	task AudioExtractTaskSpec,
	segments []string,
	maxSegmentBytes int64,
	sourceDurationSeconds float64,
	rt AudioExtractRuntime,
	allowPartial bool,
	recordStandaloneMetrics bool,
) (string, AudioExtractUsage, map[string]string, bool, error) {
	start, checkpointText := resumeAudioCheckpoint(rt.CheckpointState, len(segments))
	var combined strings.Builder
	combined.WriteString(checkpointText)
	var total AudioExtractUsage
	tags := mergeMediaExtractTags(nil, rt.CheckpointState.Tags)
	structured := rt.CheckpointState.Structured
	component := "video_extract"
	if recordStandaloneMetrics {
		component = "audio_extract"
	}

	recordSegment := func(result string) {
		if recordStandaloneMetrics {
			b.recordTenantOperation("audio_extract", "segment", result, 0)
		}
	}
	for i := start; i < len(segments); i++ {
		segment, err := readBoundedFile(segments[i], maxSegmentBytes)
		if err != nil {
			return combined.String(), total, tags, structured, fmt.Errorf("read audio segment %d: %w", i, err)
		}
		text, usage, err := b.extractAudioSegment(ctx, task, "audio/mpeg", segment, rt)
		if err != nil {
			if errors.Is(err, errAudioExtractEmptyText) {
				accumulateAudioExtractUsage(&total, usage)
				recordSegment("ok")
				if checkpointErr := b.checkpointAudioProgress(component, rt, i+1, &combined, tags, structured); checkpointErr != nil {
					return combined.String(), total, tags, structured, checkpointErr
				}
				continue
			}
			recordSegment("error")
			if !allowPartial || combined.Len() == 0 {
				return combined.String(), total, tags, structured, fmt.Errorf("audio segment %d failed: %w", i, err)
			}
			b.recordTenantOperation("audio_extract", "partial", "partial_success", 0)
			return combined.String(), total, tags, structured, fmt.Errorf("%w: segment %d", errAudioExtractPartial, i)
		}

		writeback := prepareMediaExtractWriteback(text, rt.MaxExtractTextBytes)
		structured = structured || writeback.structured
		var limited bool
		tags, limited = mergeMediaExtractTagsWithLimit(tags, writeback.tags)
		if limited {
			b.recordTenantOperation(component, "attr_skip", "attribute_limit", 0)
		}
		if strings.TrimSpace(writeback.text) != "" {
			if combined.Len() > 0 {
				combined.WriteByte('\n')
			}
			segmentSeconds := float64(audioSegmentSeconds(maxSegmentBytes))
			startSeconds := float64(i) * segmentSeconds
			endSeconds := startSeconds + segmentSeconds
			if sourceDurationSeconds > 0 {
				startSeconds = min(startSeconds, sourceDurationSeconds)
				endSeconds = min(endSeconds, sourceDurationSeconds)
			}
			combined.WriteString(audioTimeRangeHeader(startSeconds, endSeconds) + "\n" + writeback.text)
		}
		accumulateAudioExtractUsage(&total, usage)
		recordSegment("ok")
		if err := b.checkpointAudioProgress(component, rt, i+1, &combined, tags, structured); err != nil {
			return combined.String(), total, tags, structured, err
		}
	}
	return combined.String(), total, tags, structured, nil
}

func audioTimeRangeHeader(startSeconds, endSeconds float64) string {
	return fmt.Sprintf("[Audio %s-%s]", formatAudioTimestamp(startSeconds), formatAudioTimestamp(endSeconds))
}

func formatAudioTimestamp(seconds float64) string {
	totalSeconds := int64(math.Ceil(max(0, seconds)))
	hours := totalSeconds / 3600
	minutes := totalSeconds % 3600 / 60
	secs := totalSeconds % 60
	return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, secs)
}

func accumulateAudioExtractUsage(total *AudioExtractUsage, usage AudioExtractUsage) {
	total.InputTokens += usage.InputTokens
	total.OutputTokens += usage.OutputTokens
	total.DurationSeconds += usage.DurationSeconds
	total.successfulCalls++
}
