package utils

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/belphemur/CBZOptimizer/v2/internal/cbz"
	"github.com/belphemur/CBZOptimizer/v2/pkg/converter"
	errors2 "github.com/belphemur/CBZOptimizer/v2/pkg/converter/errors"
	"github.com/rs/zerolog/log"
)

type OptimizeOptions struct {
	ChapterConverter converter.Converter
	Path             string
	Quality          uint8
	Override         bool
	Split            bool
	// KeepFilenames preserves the original base filename of each page inside
	// the output CBZ (with the extension swapped for format conversion)
	// instead of the historical %04d sequential naming. Off by default so
	// existing behavior is unchanged.
	KeepFilenames bool
	Timeout       time.Duration
}

// Optimize optimizes a CBZ/CBR file using the specified converter.
// The new pipeline is disk-first:
// 1. Fast check if already converted (no extraction)
// 2. Extract archive to temp directory on disk
// 3. Convert pages file-to-file (no image data in memory)
// 4. Create output CBZ by streaming from disk
// 5. Cleanup temp files
func Optimize(options *OptimizeOptions) error {
	log.Info().Str("file", options.Path).Msg("Processing file")
	log.Debug().
		Str("file", options.Path).
		Uint8("quality", options.Quality).
		Bool("override", options.Override).
		Bool("split", options.Split).
		Bool("keep_filenames", options.KeepFilenames).
		Msg("Optimization parameters")

	// Step 1: Fast conversion check before extracting (new requirement)
	alreadyConverted, err := cbz.IsAlreadyConverted(context.Background(), options.Path)
	if err != nil {
		log.Debug().Str("file", options.Path).Err(err).Msg("Conversion check failed, proceeding with extraction")
	}
	if alreadyConverted {
		log.Info().Str("file", options.Path).Msg("Chapter already converted")
		return nil
	}

	// Step 2: Extract chapter to disk
	log.Debug().Str("file", options.Path).Msg("Extracting chapter")

	// Create context for extraction (use timeout if configured)
	var extractCtx context.Context
	if options.Timeout > 0 {
		var cancel context.CancelFunc
		extractCtx, cancel = context.WithTimeout(context.Background(), options.Timeout)
		defer cancel()
		log.Debug().Str("file", options.Path).Dur("timeout", options.Timeout).Msg("Applying timeout")
	} else {
		extractCtx = context.Background()
	}

	chapter, err := cbz.ExtractChapter(extractCtx, options.Path, options.KeepFilenames)
	if err != nil {
		log.Error().Str("file", options.Path).Err(err).Msg("Failed to extract chapter")
		return fmt.Errorf("failed to extract chapter: %w", err)
	}
	defer func() {
		if cleanupErr := chapter.Cleanup(); cleanupErr != nil {
			log.Warn().Str("file", options.Path).Err(cleanupErr).Msg("Failed to cleanup temp directory")
		}
	}()

	// Double-check conversion status from extracted metadata
	if chapter.IsConverted {
		log.Info().Str("file", options.Path).Msg("Chapter already converted")
		return nil
	}

	log.Debug().
		Str("file", options.Path).
		Int("pages", len(chapter.Pages)).
		Msg("Chapter extracted successfully")

	// Step 3: Convert pages file-to-file
	convertedChapter, err := options.ChapterConverter.ConvertChapter(extractCtx, chapter, options.Quality, options.Split, func(msg string, current uint32, total uint32) {
		if current%10 == 0 || current == total {
			log.Info().Str("file", chapter.FilePath).Uint32("current", current).Uint32("total", total).Msg("Converting")
		} else {
			log.Debug().Str("file", chapter.FilePath).Uint32("current", current).Uint32("total", total).Msg("Converting page")
		}
	})
	if err != nil {
		var pageIgnoredError *errors2.PageIgnoredError
		if errors.As(err, &pageIgnoredError) {
			log.Debug().Str("file", chapter.FilePath).Err(err).Msg("Page conversion error (non-fatal)")
		} else {
			log.Error().Str("file", chapter.FilePath).Err(err).Msg("Chapter conversion failed")
			return fmt.Errorf("failed to convert chapter: %w", err)
		}
	}
	if convertedChapter == nil {
		log.Error().Str("file", chapter.FilePath).Msg("Conversion returned nil chapter")
		return fmt.Errorf("failed to convert chapter")
	}

	log.Debug().
		Str("file", chapter.FilePath).
		Int("converted_pages", len(convertedChapter.Pages)).
		Msg("Chapter conversion completed")

	convertedChapter.SetConverted()

	// Step 4: Determine output path
	outputPath := options.Path
	originalPath := options.Path
	isCbrOverride := false

	if options.Override {
		pathLower := strings.ToLower(options.Path)
		if strings.HasSuffix(pathLower, ".cbr") {
			outputPath = strings.TrimSuffix(options.Path, filepath.Ext(options.Path)) + ".cbz"
			isCbrOverride = true
		}
	} else {
		pathLower := strings.ToLower(options.Path)
		if strings.HasSuffix(pathLower, ".cbz") {
			outputPath = strings.TrimSuffix(options.Path, ".cbz") + "_converted.cbz"
		} else if strings.HasSuffix(pathLower, ".cbr") {
			outputPath = strings.TrimSuffix(options.Path, ".cbr") + "_converted.cbz"
		} else {
			outputPath = options.Path + "_converted.cbz"
		}
	}

	// Step 5: Write converted chapter to CBZ (streaming from disk)
	log.Debug().Str("output_path", outputPath).Msg("Writing converted chapter to CBZ file")
	err = cbz.WriteChapterToCBZ(convertedChapter, outputPath)
	if err != nil {
		log.Error().Str("output_path", outputPath).Err(err).Msg("Failed to write converted chapter")
		return fmt.Errorf("failed to write converted chapter: %w", err)
	}

	// If overriding a CBR file, delete the original
	if isCbrOverride {
		err = os.Remove(originalPath)
		if err != nil {
			log.Warn().Str("file", originalPath).Err(err).Msg("Failed to delete original CBR file")
		} else {
			log.Info().Str("file", originalPath).Msg("Deleted original CBR file")
		}
	}

	log.Info().Str("output", outputPath).Msg("Converted file written")
	return nil
}
