package video

import (
	"errors"
	"io"
	"os"

	"github.com/photoprism/photoprism/pkg/fs"
)

// Reader reads an existing file from an offset until the end.
type Reader struct {
	fileName string
	file     *os.File
	offset   int64
}

// NewReader creates a new Reader.
func NewReader(fileName string, offset int64) (*Reader, error) {
	// Check if the file exists.
	if !fs.FileExists(fileName) {
		return nil, errors.New("file not found")
	}

	// Open file for reading.
	file, err := os.Open(fileName) //nolint:gosec // fileName validated by caller

	if err != nil {
		return nil, err
	}

	// Ensure that the offset is positive and we are starting at the right position.
	if offset < 0 {
		offset = 0
	} else if offset > 0 {
		if _, seekErr := file.Seek(offset, io.SeekStart); seekErr != nil {
			_ = file.Close()
			return nil, err
		}
	}

	// Create new reader and return it.
	return &Reader{fileName: fileName, file: file, offset: int64(offset)}, nil
}

// Read reads up to len(p) bytes into p. It returns the number of bytes read and any error encountered.
func (r *Reader) Read(p []byte) (n int, err error) {
	return r.file.Read(p)
}

// Close closes the file after reading.
func (r *Reader) Close() error {
	r.fileName = ""
	r.offset = 0
	return r.file.Close()
}

// ReadSeeker reads an existing file from an offset until the end.
type ReadSeeker struct {
	file   io.ReadSeeker
	offset int64
}

// NewReadSeeker creates a new ReadSeeker.
func NewReadSeeker(file io.ReadSeeker, offset int64) *ReadSeeker {
	// Ensure that the offset is positive.
	if offset < 0 {
		offset = 0
	}

	return &ReadSeeker{
		file:   file,
		offset: offset,
	}
}

// Read reads up to len(p) bytes into p. It returns the number of bytes read and any error encountered.
func (r *ReadSeeker) Read(p []byte) (n int, err error) {
	return r.file.Read(p)
}

// Seek sets the offset for the next Read or Write to offset, interpreted according to whence:
// - SeekStart means relative to the start of the file
// - SeekCurrent means relative to the current offset
// - SeekEnd means relative to the end (for example, offset = -2 specifies the penultimate byte of the file)
//
// Seek returns the new offset relative to the start of the file or an error, if any.
// Seeking to an offset before the start of the file is an error.
func (r *ReadSeeker) Seek(offset int64, whence int) (pos int64, err error) {
	switch whence {
	case io.SeekStart:
		pos, err = r.file.Seek(offset+r.offset, whence)
	case io.SeekCurrent, io.SeekEnd:
		pos, err = r.file.Seek(offset, whence)
	default:
		return 0, errors.New("unknown whence")
	}

	return pos - r.offset, err
}
