package service

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"dubbo.apache.org/dubbo-go/v3/common/logger"
	filesApi "github.com/filebrowser/filebrowser/v2/api/files"
	"github.com/filebrowser/filebrowser/v2/files"
	"github.com/filebrowser/filebrowser/v2/rules"
	"github.com/filebrowser/filebrowser/v2/search"
	"github.com/spf13/afero"
)

type FilesProvider struct {
	filesApi.UnimplementedFileServer
}

const BASE_PATH = "../"

func (f *FilesProvider) List(ctx context.Context, req *filesApi.FileListReq) (*filesApi.FileListResp, error) {
	file, err := files.NewFileInfo(&files.FileOptions{
		Fs:         getFs(req.UserSpacePath),
		Path:       req.Path,
		Modify:     true,
		Expand:     true,
		ReadHeader: true,
		Content:    true,
		Checker:    rules.EmptyChecker,
	})
	if err != nil {
		return nil, err
	}
	if !file.IsDir {
		return nil, errors.New("路径不是文件夹")
	}
	file.Listing.Sorting.Asc = req.Sorting.Asc
	file.Listing.Sorting.By = req.Sorting.By
	file.Listing.ApplySort()
	result := &filesApi.FileListResp{
		NumDirs:  int32(file.NumDirs),
		NumFiles: int32(file.NumFiles),
		Sorting: &filesApi.Sorting{
			By:  file.Sorting.By,
			Asc: file.Sorting.Asc,
		},
		Path:      file.Path,
		Name:      file.Name,
		Size:      file.Size,
		Extension: file.Extension,
		Mode:      file.Mode.String(),
		IsDir:     file.IsDir,
		IsSymlink: file.IsSymlink,
		Type:      file.Type,
		Items:     make([]*filesApi.Items, len(file.Listing.Items)),
	}
	for _, v := range file.Listing.Items {
		result.Items = append(result.Items, &filesApi.Items{
			Path:      v.Path,
			Name:      v.Name,
			Size:      v.Size,
			Extension: v.Extension,
			Mode:      v.Mode.String(),
			IsDir:     v.IsDir,
			IsSymlink: v.IsSymlink,
			Type:      v.Type,
		})
	}
	return result, err
}

func (f *FilesProvider) Create(ctx context.Context, req *filesApi.CreateReq) (*filesApi.CreateResp, error) {
	fs := getFs(req.UserSpacePath)
	return new(filesApi.CreateResp), fs.MkdirAll(req.Path, files.PermDir)
}

func (f *FilesProvider) Delete(ctx context.Context, req *filesApi.DeleteReq) (*filesApi.DeleteResp, error) {
	fs := getFs(req.UserSpacePath)
	return new(filesApi.DeleteResp), fs.RemoveAll(req.Path)
}

func (f *FilesProvider) Upload(ctx context.Context, req *filesApi.UploadReq) (*filesApi.UploadResp, error) {
	fs := getFs(req.UserSpacePath)
	fi, err := fs.Create(req.Path)
	if err != nil {
		return nil, err
	}
	defer fi.Close()
	_, err = fi.Write(req.Content)
	if err != nil {
		return nil, err
	}
	return new(filesApi.UploadResp), nil
}

func (f *FilesProvider) Search(ctx context.Context, req *filesApi.SearchReq) (*filesApi.SearchResp, error) {
	fs := getFs(req.UserSpacePath)
	result := new(filesApi.SearchResp)
	err := search.Search(fs, req.Path, req.Query, rules.EmptyChecker, func(path string, f os.FileInfo) error {
		result.Items = append(result.Items, &filesApi.SearchResp_Nested{
			Dir:  f.IsDir(),
			Path: path,
		})

		return nil
	})

	if err != nil {
		return nil, err
	}
	return result, err
}

func (f *FilesProvider) TusCreate(ctx context.Context, req *filesApi.TusCreateReq) (*filesApi.TusCreateResp, error) {
	fs := getFs(req.UserSpacePath)
	file, err := files.NewFileInfo(&files.FileOptions{
		Fs:         fs,
		Path:       req.Path,
		Modify:     true,
		Expand:     false,
		ReadHeader: true,
		Checker:    rules.EmptyChecker,
	})
	switch {
	case errors.Is(err, afero.ErrFileNotFound):
		dirPath := filepath.Dir(req.Path)
		if _, statErr := fs.Stat(dirPath); os.IsNotExist(statErr) {
			if mkdirErr := fs.MkdirAll(dirPath, files.PermDir); mkdirErr != nil {
				return nil, err
			}
		}
	case err != nil:
		return nil, err
	}
	fileFlags := os.O_CREATE | os.O_WRONLY
	if req.Override {
		fileFlags |= os.O_TRUNC
	}

	// if file exists
	if file != nil {
		if file.IsDir {
			return nil, fmt.Errorf("上传路径不能是文件夹 %s", file.RealPath())
		}
	}

	openFile, err := fs.OpenFile(req.Path, fileFlags, files.PermFile)
	if err != nil {
		return nil, err
	}
	if err := openFile.Close(); err != nil {
		return nil, err
	}
	logger.Infof("创建空文件，%v", openFile.Name())
	return &filesApi.TusCreateResp{
		UploadLength: -1,
		UploadOffset: 0,
	}, nil
}

func (f *FilesProvider) TusUpload(ctx context.Context, req *filesApi.TusUploadReq) (*filesApi.TusUploadResp, error) {
	fs := getFs(req.UserSpacePath)
	file, err := files.NewFileInfo(&files.FileOptions{
		Fs:         fs,
		Path:       req.Path,
		Modify:     true,
		Expand:     false,
		ReadHeader: true,
		Checker:    rules.EmptyChecker,
	})
	switch {
	case errors.Is(err, afero.ErrFileNotFound):
		return nil, errors.New("未找到文件")
	case err != nil:
		return nil, err
	}

	switch {
	case file.IsDir:
		return nil, fmt.Errorf("上传路径不能是文件夹 %s", file.RealPath())
	case file.Size != req.UploadOffset:
		return nil, fmt.Errorf(
			"%s 文件大小符合: %d",
			file.RealPath(),
			req.UploadOffset,
		)
	}
	openFile, err := fs.OpenFile(req.Path, os.O_WRONLY|os.O_APPEND, files.PermFile)
	if err != nil {
		return nil, fmt.Errorf("could not open file: %w", err)
	}
	defer openFile.Close()

	_, err = openFile.Seek(req.UploadOffset, 0)
	if err != nil {
		return nil, fmt.Errorf("could not seek file: %w", err)
	}

	logger.Infof("写入文件块，offset:%v，块大小：%vbyte", req.UploadOffset, len(req.Content))
	bytesWritten, err := io.Copy(openFile, bytes.NewBuffer(req.Content))
	if err != nil {
		return nil, fmt.Errorf("could not write to file: %w", err)
	}
	return &filesApi.TusUploadResp{
		UploadOffset: bytesWritten,
	}, nil
}

func (f *FilesProvider) Info(ctx context.Context, req *filesApi.FileInfoReq) (*filesApi.FileInfoResp, error) {
	fs := getFs(req.UserSpacePath)
	file, err := files.NewFileInfo(&files.FileOptions{
		Fs:         fs,
		Path:       req.Path,
		Modify:     true,
		Expand:     false,
		ReadHeader: true,
		Checker:    rules.EmptyChecker,
	})
	switch {
	case errors.Is(err, afero.ErrFileNotFound):
		return nil, errors.New("未找到文件")
	case err != nil:
		return nil, err
	}
	result := &filesApi.FileInfoResp{
		Path:      file.Path,
		Name:      file.Name,
		Size:      file.Size,
		Extension: file.Extension,
		Mode:      file.Mode.String(),
		IsDir:     file.IsDir,
		IsSymlink: file.IsSymlink,
		Type:      file.Type,
	}
	return result, nil
}

func (f *FilesProvider) ResumableTransfer(ctx context.Context, req *filesApi.ResumableTransferReq) (*filesApi.ResumableTransferResp, error) {
	fs := getFs(req.UserSpacePath)
	_, err := files.NewFileInfo(&files.FileOptions{
		Fs:         fs,
		Path:       req.Path,
		Modify:     true,
		Expand:     false,
		ReadHeader: true,
		Checker:    rules.EmptyChecker,
	})
	switch {
	case errors.Is(err, afero.ErrFileNotFound):
		return nil, errors.New("未找到文件")
	case err != nil:
		return nil, err
	}
	openFile, err := fs.Open(req.Path)
	if err != nil {
		return nil, fmt.Errorf("could not open file: %w", err)
	}
	defer openFile.Close()
	logger.Debugf("设置文件读取，offset:%v，length：%v", req.Offset, req.Length)
	_, err = openFile.Seek(req.Offset, 0)
	if err != nil {
		return nil, err
	}
	b := make([]byte, req.Length)
	n, err := openFile.Read(b)
	if err != nil {
		return nil, err
	}
	return &filesApi.ResumableTransferResp{
		Content: b[:n],
	}, nil
}

func getFs(UserSpacePath string) afero.Fs {
	bashAbs, _ := filepath.Abs(BASE_PATH)
	if !strings.HasPrefix(UserSpacePath, "/") {
		UserSpacePath = "/" + UserSpacePath
	}
	return afero.NewBasePathFs(afero.NewOsFs(), bashAbs+UserSpacePath)
}
