package crypto

import (
	"cachefs/pkg/provider"
	osp "cachefs/pkg/provider/os"
	"encoding/base64"
	"errors"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
)

var _ provider.FS = (*FS)(nil)

type FS struct {
	fs  provider.FS
	cbc *aesCbc
	key []byte
}

func NewFS(fs provider.FS, key []byte) (*FS, error) {
	if len(key) != 32 {
		return nil, errors.New("encryption key length must be 32 bytes")
	}
	cbc, err := newAesCbc(key)
	if err != nil {
		return nil, err
	}
	return &FS{
		fs:  fs,
		cbc: cbc,
		key: key,
	}, nil
}

func (fs *FS) encPath(path string) string {
	parts := strings.Split(path, "/")
	cparts := make([]string, len(parts))
	for i, p := range parts {
		if p == "" {
			continue
		}
		cparts[i] = base64.RawURLEncoding.EncodeToString(
			fs.cbc.encrypt([]byte(p)),
		)
	}
	return filepath.Join(cparts...)
}

func (fs *FS) decPath(path string) (string, error) {
	parts := strings.Split(path, "/")
	pparts := make([]string, len(parts))
	for i, p := range parts {
		b, err := base64.RawURLEncoding.DecodeString(p)
		if err != nil {
			return "", err
		}
		pparts[i] = string(fs.cbc.decrypt(b))
	}
	return filepath.Join(pparts...), nil
}

func (fs *FS) name(p string) string {
	return filepath.Join(fs.Root(), p)
}

func (fs *FS) Stat(p string) (fs.FileInfo, error) {
	fi, err := fs.fs.Stat(fs.encPath(p))
	return stat{FileInfo: fi, name: fs.name(p)}, err
}

func (fs *FS) Remove(p string) error {
	return fs.fs.Remove(fs.encPath(p))
}

func (fs *FS) Open(p string) (provider.File, error) {
	c := fs.encPath(p)
	fi, err := fs.fs.Stat(c)
	if err != nil {
		return nil, err
	}
	f, err := fs.fs.Open(c)
	if err != nil {
		return nil, err
	}
	return newFile(fs.key, p, fs, f, fi.IsDir())
}

func (fs *FS) OpenFile(p string, flags int, mode os.FileMode) (provider.File, error) {
	f, err := fs.fs.OpenFile(fs.encPath(p), flags, mode)
	if err != nil {
		return nil, err
	}
	return newFile(fs.key, p, fs, f, false)
}

func (fs *FS) MkdirAll(p string, mode os.FileMode) error {
	return fs.fs.MkdirAll(fs.encPath(p), mode)
}

func (fs *FS) Root() string {
	return fs.fs.Root()
}

func (fs *FS) Fstat(fd uintptr) (int64, error) {
	osp, ok := fs.fs.(*osp.FS)
	if ok {
		return osp.Fstat(fd)
	}
	return 0, provider.ErrFstat
}

func (fs *FS) Close() error {
	return nil
}
