package archive

import (
	"context"
	"io"
	"io/fs"
	"path"
	"strings"
	"sync"
	"time"

	"git.kmsign.ru/royalcat/tstor/server/src/vfs"
)

// Unlink implements Filesystem.
func (a *ArchiveFS) Unlink(ctx context.Context, filename string) error {
	return vfs.ErrNotImplemented
}

func (a *ArchiveFS) Open(ctx context.Context, filename string) (vfs.File, error) {
	if filename == vfs.Separator {
		return vfs.NewDirFile(filename), nil
	}

	f, ok := a.files[filename]
	if ok {
		return f.open(ctx)
	}

	for p := range a.files {
		if strings.HasPrefix(p, filename) {
			return vfs.NewDirFile(filename), nil
		}
	}

	return nil, vfs.ErrNotExist
}

func (a *ArchiveFS) ReadDir(ctx context.Context, path string) ([]fs.DirEntry, error) {
	infos := make(map[string]fs.FileInfo, len(a.files))
	for k, v := range a.files {
		infos[k] = v
	}

	return vfs.ListDirFromInfo(infos, path)
}

// Stat implements Filesystem.
func (afs *ArchiveFS) Stat(ctx context.Context, filename string) (fs.FileInfo, error) {
	if entry, ok := afs.files[filename]; ok {
		return entry, nil
	}

	for p := range afs.files {
		if strings.HasPrefix(p, filename) {
			return vfs.NewDirInfo(path.Base(filename), time.Time{}), nil
		}
	}

	return nil, vfs.ErrNotExist
}

// Info implements Filesystem.
func (a *ArchiveFS) Info() (fs.FileInfo, error) {
	return a, nil
}

// IsDir implements Filesystem.
func (a *ArchiveFS) IsDir() bool {
	return true
}

// Name implements Filesystem.
func (a *ArchiveFS) Name() string {
	return a.name
}

// Type implements Filesystem.
func (a *ArchiveFS) Type() fs.FileMode {
	return fs.ModeDir
}

var _ vfs.File = (*archiveFile)(nil)

func newArchiveFile(name string, size int64, rr *randomReaderFromLinear) *archiveFile {
	return &archiveFile{
		name: name,
		size: size,
		rr:   rr,
	}
}

type archiveFile struct {
	name string
	size int64

	m      sync.Mutex
	offset int64

	rr *randomReaderFromLinear
}

// Seek implements File.
func (d *archiveFile) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case io.SeekStart:
		d.offset = offset

	case io.SeekCurrent:
		d.offset += offset
	case io.SeekEnd:
		d.offset = d.size + offset
	}
	return d.offset, nil
}

// Name implements File.
func (d *archiveFile) Name() string {
	return d.name
}

// Type implements File.
func (d *archiveFile) Type() fs.FileMode {
	return vfs.ModeFileRO
}

func (d *archiveFile) Info() (fs.FileInfo, error) {
	return vfs.NewFileInfo(d.name, d.size, time.Time{}), nil
}

func (d *archiveFile) Size() int64 {
	return d.size
}

func (d *archiveFile) IsDir() bool {
	return false
}

func (d *archiveFile) Read(ctx context.Context, p []byte) (n int, err error) {
	ctx, span := tracer.Start(ctx, "archive.File.Read")
	defer span.End()

	n, err = d.rr.ReadAt(ctx, p, d.offset)
	d.offset += int64(n)
	return n, err
}

func (d *archiveFile) ReadAt(ctx context.Context, p []byte, off int64) (n int, err error) {
	d.m.Lock()
	defer d.m.Unlock()

	return d.rr.ReadAt(ctx, p, off)
}

func (d *archiveFile) Close(ctx context.Context) error {
	// FIXME close should do nothing as archive fs currently reuse the same file instances
	return nil
}