// File-based capture for testing and development.
package backends

import (
	"fmt"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"os"
	"path/filepath"
	"strings"
	"time"

	"git.cloonar.com/openclawd/iso-bot/pkg/engine/capture"
)

// FileConfig holds configuration for file-based capture.
type FileConfig struct {
	// Path is the file or directory path to read from.
	Path string `yaml:"path"`

	// Type specifies the input type: "image", "video", "sequence", "directory".
	Type string `yaml:"type"`

	// FrameRate for video playback or image sequence (frames per second).
	FrameRate float64 `yaml:"frame_rate"`

	// Loop enables looping for video or image sequences.
	Loop bool `yaml:"loop"`

	// Pattern is the filename pattern for image sequences (e.g., "frame_%04d.png").
	Pattern string `yaml:"pattern"`

	// StartFrame for image sequences or video (0-based).
	StartFrame int `yaml:"start_frame"`

	// EndFrame for image sequences or video (-1 for end of sequence).
	EndFrame int `yaml:"end_frame"`
}

// FileSource provides capture from static images, image sequences, or video files.
type FileSource struct {
	config       FileConfig
	currentFrame int
	totalFrames  int
	frameData    []image.Image
	lastCapture  time.Time
	frameInterval time.Duration
	width        int
	height       int
}

// NewFileSource creates a file-based capture source.
func NewFileSource(configMap map[string]interface{}) (capture.Source, error) {
	var config FileConfig

	// Extract config from map
	if path, ok := configMap["path"].(string); ok {
		config.Path = path
	} else {
		return nil, fmt.Errorf("file path is required")
	}

	if fileType, ok := configMap["type"].(string); ok {
		config.Type = fileType
	} else {
		config.Type = "auto" // Auto-detect based on path
	}

	if frameRate, ok := configMap["frame_rate"].(float64); ok {
		config.FrameRate = frameRate
	} else {
		config.FrameRate = 30.0 // Default 30 FPS
	}

	if loop, ok := configMap["loop"].(bool); ok {
		config.Loop = loop
	} else {
		config.Loop = true // Default to looping
	}

	if pattern, ok := configMap["pattern"].(string); ok {
		config.Pattern = pattern
	}

	if startFrame, ok := configMap["start_frame"].(int); ok {
		config.StartFrame = startFrame
	}

	if endFrame, ok := configMap["end_frame"].(int); ok {
		config.EndFrame = endFrame
	} else {
		config.EndFrame = -1 // End of sequence
	}

	source := &FileSource{
		config:        config,
		currentFrame:  config.StartFrame,
		frameInterval: time.Duration(float64(time.Second) / config.FrameRate),
	}

	// Load file(s) and initialize
	if err := source.initialize(); err != nil {
		return nil, fmt.Errorf("failed to initialize file source: %w", err)
	}

	return source, nil
}

// Name returns a description of this capture source.
func (f *FileSource) Name() string {
	return fmt.Sprintf("File: %s", filepath.Base(f.config.Path))
}

// Capture grabs a single frame from the file source.
func (f *FileSource) Capture() (image.Image, error) {
	if len(f.frameData) == 0 {
		return nil, fmt.Errorf("no frames loaded")
	}

	// Respect frame rate timing
	if !f.lastCapture.IsZero() {
		elapsed := time.Since(f.lastCapture)
		if elapsed < f.frameInterval {
			time.Sleep(f.frameInterval - elapsed)
		}
	}
	f.lastCapture = time.Now()

	// Get current frame
	if f.currentFrame >= len(f.frameData) {
		if f.config.Loop {
			f.currentFrame = f.config.StartFrame
		} else {
			return nil, fmt.Errorf("end of sequence reached")
		}
	}

	frame := f.frameData[f.currentFrame]
	f.currentFrame++

	// Check end frame limit
	if f.config.EndFrame > 0 && f.currentFrame > f.config.EndFrame {
		if f.config.Loop {
			f.currentFrame = f.config.StartFrame
		} else {
			return frame, fmt.Errorf("end frame reached")
		}
	}

	return frame, nil
}

// CaptureRegion grabs a sub-region of the current frame.
func (f *FileSource) CaptureRegion(r capture.Region) (image.Image, error) {
	fullFrame, err := f.Capture()
	if err != nil {
		return nil, err
	}

	// Crop the image to the specified region
	bounds := image.Rect(r.X, r.Y, r.X+r.Width, r.Y+r.Height)
	
	// Check if the region is within bounds
	imgBounds := fullFrame.Bounds()
	if !bounds.In(imgBounds) {
		bounds = bounds.Intersect(imgBounds)
		if bounds.Empty() {
			return nil, fmt.Errorf("region is outside image bounds")
		}
	}

	return fullFrame.(interface{
		SubImage(r image.Rectangle) image.Image
	}).SubImage(bounds), nil
}

// Size returns the frame dimensions.
func (f *FileSource) Size() (width, height int) {
	return f.width, f.height
}

// Close releases file resources.
func (f *FileSource) Close() error {
	// Clear frame data to free memory
	f.frameData = nil
	return nil
}

// initialize loads the file(s) and prepares frame data.
func (f *FileSource) initialize() error {
	// Auto-detect type if not specified
	if f.config.Type == "auto" {
		f.config.Type = f.detectType()
	}

	switch f.config.Type {
	case "image":
		return f.loadSingleImage()
	case "sequence":
		return f.loadImageSequence()
	case "directory":
		return f.loadDirectory()
	case "video":
		return f.loadVideo()
	default:
		return fmt.Errorf("unsupported file type: %s", f.config.Type)
	}
}

// detectType automatically determines the file type.
func (f *FileSource) detectType() string {
	info, err := os.Stat(f.config.Path)
	if err != nil {
		return "image" // Default fallback
	}

	if info.IsDir() {
		return "directory"
	}

	ext := strings.ToLower(filepath.Ext(f.config.Path))
	switch ext {
	case ".png", ".jpg", ".jpeg", ".gif", ".bmp":
		return "image"
	case ".mp4", ".avi", ".mov", ".mkv", ".webm":
		return "video"
	default:
		// Check if it looks like a sequence pattern
		if strings.Contains(f.config.Path, "%") || strings.Contains(f.config.Path, "*") {
			return "sequence"
		}
		return "image"
	}
}

// loadSingleImage loads a single image file.
func (f *FileSource) loadSingleImage() error {
	img, err := f.loadImage(f.config.Path)
	if err != nil {
		return err
	}

	f.frameData = []image.Image{img}
	f.totalFrames = 1
	f.width = img.Bounds().Dx()
	f.height = img.Bounds().Dy()

	return nil
}

// loadImageSequence loads a numbered sequence of images.
func (f *FileSource) loadImageSequence() error {
	var frames []image.Image
	
	// Generate filenames based on pattern
	for i := f.config.StartFrame; f.config.EndFrame < 0 || i <= f.config.EndFrame; i++ {
		filename := fmt.Sprintf(f.config.Pattern, i)
		if !filepath.IsAbs(filename) {
			filename = filepath.Join(filepath.Dir(f.config.Path), filename)
		}

		// Check if file exists
		if _, err := os.Stat(filename); os.IsNotExist(err) {
			if len(frames) == 0 {
				return fmt.Errorf("no images found matching pattern: %s", f.config.Pattern)
			}
			break // End of sequence
		}

		img, err := f.loadImage(filename)
		if err != nil {
			return fmt.Errorf("failed to load %s: %w", filename, err)
		}

		frames = append(frames, img)
	}

	if len(frames) == 0 {
		return fmt.Errorf("no frames loaded from sequence")
	}

	f.frameData = frames
	f.totalFrames = len(frames)
	f.width = frames[0].Bounds().Dx()
	f.height = frames[0].Bounds().Dy()

	return nil
}

// loadDirectory loads all images from a directory.
func (f *FileSource) loadDirectory() error {
	entries, err := os.ReadDir(f.config.Path)
	if err != nil {
		return err
	}

	var frames []image.Image
	
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		filename := entry.Name()
		ext := strings.ToLower(filepath.Ext(filename))
		
		// Check if it's a supported image format
		if !isImageFile(ext) {
			continue
		}

		fullPath := filepath.Join(f.config.Path, filename)
		img, err := f.loadImage(fullPath)
		if err != nil {
			continue // Skip files that can't be loaded
		}

		frames = append(frames, img)
	}

	if len(frames) == 0 {
		return fmt.Errorf("no image files found in directory: %s", f.config.Path)
	}

	f.frameData = frames
	f.totalFrames = len(frames)
	f.width = frames[0].Bounds().Dx()
	f.height = frames[0].Bounds().Dy()

	return nil
}

// loadVideo loads frames from a video file.
func (f *FileSource) loadVideo() error {
	// TODO: Implement video decoding
	// This would require a video decoding library like FFmpeg
	// For now, return an error
	return fmt.Errorf("video capture not implemented yet - use image sequences instead")
}

// loadImage loads a single image file.
func (f *FileSource) loadImage(path string) (image.Image, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	// Determine format based on file extension
	ext := strings.ToLower(filepath.Ext(path))
	switch ext {
	case ".png":
		return png.Decode(file)
	case ".jpg", ".jpeg":
		return jpeg.Decode(file)
	case ".gif":
		return gif.Decode(file)
	default:
		// Try to decode generically
		img, _, err := image.Decode(file)
		return img, err
	}
}

// isImageFile checks if the file extension indicates an image file.
func isImageFile(ext string) bool {
	switch ext {
	case ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".webp":
		return true
	default:
		return false
	}
}