package disk

import (
	"io"
	"io/fs"
	"time"
)

// File interface extends the fs.File interface to include io.Writer.
// This is because the standard fs package is modeled after a read-only File
// System, but we need to write to files as well. The standard os.File type will
// satisfy this interface, as it implements both fs.File and io.Writer.
type File interface {
	fs.File
	io.Writer
}

// Disk interface defines the methods for a disk abstraction layer. This allows
// us to easily create mock disks for testing purposes, or to simulate different
// levels of failure and latency in a simuated environment.
type Disk interface {
	// Mkdir creates a directory with the specified name and permissions.
	Mkdir(name string, perm fs.FileMode) error
	// MkdirAll creates a directory and any necessary parent directories.
	MkdirAll(name string, perm fs.FileMode) error
	// Create creates a new file with the specified name and returns a File interface.
	Create(name string) (File, error)
	// Stat retrieves the file information for the specified name, returning
	// fs.FileInfo and an error if any.
	Stat(name string) (fs.FileInfo, error)
	// Open opens an existing file with the specified name and returns a File interface.
	Open(name string) (File, error)
	// Remove deletes the file or directory with the specified name.
	Remove(name string) error
}

type MockDisk struct {
	MkdirFunc    func(name string, perm fs.FileMode) error
	MkdirAllFunc func(name string, perm fs.FileMode) error
	CreateFunc   func(name string) (File, error)
	StatFunc     func(name string) (fs.FileInfo, error)
	OpenFunc     func(name string) (File, error)
	RemoveFunc   func(name string) error
}

var _ Disk = &MockDisk{}

func (t *MockDisk) Mkdir(name string, perm fs.FileMode) error {
	if t.MkdirFunc != nil {
		return t.MkdirFunc(name, perm)
	}
	return nil
}

func (t *MockDisk) MkdirAll(name string, perm fs.FileMode) error {
	if t.MkdirAllFunc != nil {
		return t.MkdirAllFunc(name, perm)
	}
	return nil
}

func (t *MockDisk) Create(name string) (File, error) {
	if t.CreateFunc != nil {
		return t.CreateFunc(name)
	}
	return nil, nil
}

func (t *MockDisk) Stat(name string) (fs.FileInfo, error) {
	if t.StatFunc != nil {
		return t.StatFunc(name)
	}
	return nil, nil
}

func (t *MockDisk) Open(name string) (File, error) {
	if t.OpenFunc != nil {
		return t.OpenFunc(name)
	}
	return nil, nil
}

func (t *MockDisk) Remove(name string) error {
	if t.RemoveFunc != nil {
		return t.RemoveFunc(name)
	}
	return nil
}

type MockFileInfo struct {
	NameFunc    func() string
	SizeFunc    func() int64
	IsDirFunc   func() bool
	ModeFunc    func() fs.FileMode
	ModTimeFunc func() fs.FileInfo
	SysFunc     func() any
}

var _ fs.FileInfo = &MockFileInfo{}

// IsDir implements fs.FileInfo.
func (t *MockFileInfo) IsDir() bool {
	if t.IsDirFunc != nil {
		return t.IsDirFunc()
	}
	return false
}

// ModTime implements fs.FileInfo.
func (t *MockFileInfo) ModTime() time.Time {
	if t.ModTimeFunc != nil {
		return t.ModTimeFunc().ModTime()
	}
	return time.Time{}
}

// Mode implements fs.FileInfo.
func (t *MockFileInfo) Mode() fs.FileMode {
	if t.ModeFunc != nil {
		return t.ModeFunc()
	}
	return 0
}

// Name implements fs.FileInfo.
func (t *MockFileInfo) Name() string {
	if t.NameFunc != nil {
		return t.NameFunc()
	}
	return ""
}

// Size implements fs.FileInfo.
func (t *MockFileInfo) Size() int64 {
	if t.SizeFunc != nil {
		return t.SizeFunc()
	}
	return 0
}

// Sys implements fs.FileInfo.
func (t *MockFileInfo) Sys() any {
	if t.SysFunc != nil {
		return t.SysFunc()
	}
	return nil
}