package smb

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io/fs"
	"path/filepath"
	"sync/atomic"

	"imuslab.com/smb/driver/mod/smb/encoder"
)

func init() {
	commandRequestMap[CommandFind] = func() DataI {
		return &QueryDirectoryRequest{}
	}
}

type FindFlags uint8

const (
	RestartScans      FindFlags = 0x0001
	ReturnSingleEntry FindFlags = 0x0002
	IndexSpecified    FindFlags = 0x0004
	Reopen            FindFlags = 0x0010
)

func (c FindFlags) MarshalBinary(meta *encoder.Metadata) ([]byte, error) {
	return MarshalBinary(c, meta, c)
}
func (c FindFlags) UnmarshalBinary(data []byte, meta *encoder.Metadata) (interface{}, error) {
	return UnmarshalBinary(c, data, meta, c)
}

type QueryDirectoryRequest struct {
	Header
	StructureSize  uint16
	InfoLevel      FileInformationClass
	FindFlags      FindFlags //1: restart scan
	FileIndex      uint32
	FileId         GUID
	FileNameOffset uint16 `smb:"offset:FileName"`
	FileNameLength uint16 `smb:"len:FileName"`
	// BlobOffset         uint16 `smb:"offset:Blob"`
	// BlobLength         uint16 `smb:"len:Blob"`
	OutputBufferLength uint32
	// Blob               []byte //unicode
	FileName []byte //unicode

}

type QueryDirectoryResponse struct {
	Header
	StructureSize      uint16
	OutputBufferOffset uint16 `smb:"offset:OutputBuffer"`
	OutputBufferLength uint32 `smb:"len:OutputBuffer"`
	OutputBuffer       []byte
}

var KFILEID = uint64(0)

func (data *QueryDirectoryRequest) ServerAction(ctx *DataCtx) (interface{}, error) {
	data.Header.Flags = SMB2_FLAGS_RESPONSE

	fileid := ctx.FileID(data.FileId)
	webfile, ok := ctx.session.openedFiles[fileid]
	if !ok {
		return ERR(data.Header, STATUS_FILE_CLOSED)
	}
	stat, err := webfile.Stat()
	if err != nil {
		return ERR(data.Header, STATUS_INVALID_PARAMETER)
	}
	if !stat.IsDir() {
		if (data.FindFlags & Reopen) != 0 {
			return ERR(data.Header, STATUS_NOT_SUPPORTED)
		} else {
			return ERR(data.Header, STATUS_INVALID_PARAMETER)
		}
	}

	getFileDirInfo := func(fi fs.FileInfo) *FileDirectoryInfo {
		mtime := timeToFiletime(fi.ModTime())
		filename := filepath.Base(fi.Name())
		nameByte := encoder.ToUnicode(filename)
		var fa FileAttributes
		var AllocationSize, EndOfFile uint64
		if fi.IsDir() {
			fa |= FILE_ATTRIBUTE_DIRECTORY
		} else {
			// fa |= FILE_ATTRIBUTE_ARCHIVE
			fa |= FILE_ATTRIBUTE_NORMAL
			AllocationSize = uint64(fi.Size()) //磁盘大小, 会有碎片, 比endoffile大一点
			EndOfFile = uint64(fi.Size())      //文件大小
		}

		// logx.Printf("fa: %v", fa)
		return &FileDirectoryInfo{
			CreateTime:     mtime,
			LastAccessTime: mtime,
			LastWriteTime:  mtime,
			LastChangeTime: mtime,
			FileName:       nameByte,
			AllocationSize: AllocationSize, //磁盘大小, 会有碎片, 比endoffile大一点
			EndOfFile:      EndOfFile,      //文件大小
			FileAttributes: fa,
		}
	}

	getFileIdBothDirInfo := func(fi fs.FileInfo) *FileIdBothDirectoryInfo {
		mtime := timeToFiletime(fi.ModTime())
		filename := filepath.Base(fi.Name())
		nameByte := encoder.ToUnicode(filename)
		var fa FileAttributes
		size := uint32(fi.Size())
		var AllocationSize, EndOfFile uint64
		if fi.IsDir() {
			fa |= FILE_ATTRIBUTE_DIRECTORY
			// logx.Printf("fa if: %v", fa)
			//TODO
			size = 0x011111111
		} else {
			// fa |= FILE_ATTRIBUTE_ARCHIVE
			fa |= FILE_ATTRIBUTE_NORMAL
			AllocationSize = uint64(fi.Size()) //磁盘大小, 会有碎片, 比endoffile大一点
			EndOfFile = uint64(fi.Size())      //文件大小
			if size == 0 {
				size = 0x011111111
			}
		}

		// logx.Printf("fa: %v", fa)
		fid := atomic.AddUint64(&KFILEID, 1)
		return &FileIdBothDirectoryInfo{
			CreateTime:     mtime,
			LastAccessTime: mtime,
			LastWriteTime:  mtime,
			LastChangeTime: mtime,
			FileName:       nameByte,
			AllocationSize: AllocationSize, //磁盘大小, 会有碎片, 比endoffile大一点
			EndOfFile:      EndOfFile,      //文件大小
			FileAttributes: fa,
			FileId:         fid,
			EASize:         size,
		}
	}

	getFileInfo := func(fi fs.FileInfo, level FileInformationClass) ([]byte, error) {
		if level == FileDirectoryInformation {
			return encoder.Marshal(getFileDirInfo(fi))
		} else if level == FileIdBothDirectoryInformation {
			return encoder.Marshal(getFileIdBothDirInfo(fi))
		} else {
			return nil, errors.New("Unsupported")
		}
	}

	FileName, err := encoder.FromUnicode(data.FileName)
	if err != nil {
		return ERR(data.Header, STATUS_INVALID_PARAMETER)
	}

	var OutputBuffer []byte
	// if data.FindFlags == 0 {
	// 	return ERR(data.Header, STATUS_NO_MORE_FILES)
	// }

	// data.FindFlags == 0   RestartScans
	switch data.InfoLevel {
	case FileDirectoryInformation, FileIdBothDirectoryInformation:
		switch FileName {
		case "*":
			fis, err := webfile.Readdir(0)
			if err != nil {
				return nil, err
			}
			var items [][]byte

			// selfFI, err := webfile.Stat()
			// if err != nil {
			// 	return nil, err
			// }
			// fis = append(fis, &fileInfoX{FileInfo: selfFI, name: "."})
			// fis = append(fis, &fileInfoX{FileInfo: selfFI, name: ".."})

			for _, fi := range fis {
				itemBuf, err := getFileInfo(fi, data.InfoLevel)
				if err != nil {
					//如果有错误, 就继续. 这个看以后是否修改.
					continue
				}
				items = append(items, itemBuf)
				if (data.FindFlags & ReturnSingleEntry) != 0 {
					break
				}
			}
			if true {
				for i := 0; i < len(items); i++ {
					//需要设置每个包的大小.用来分割包.最后一个包不用.
					if i != len(items)-1 {
						line := items[i]
						binary.LittleEndian.PutUint32(line, uint32(len(line)))
					}
				}
				OutputBuffer = bytes.Join(items, []byte{})
			}
			data.Header.Status = StatusOk
			if len(items) == 0 {
				return ERR(data.Header, STATUS_NO_MORE_FILES)
			}
		default:
			fis, err := webfile.Readdir(0)
			if err != nil {
				return nil, err
			}
			var fi fs.FileInfo
			for _, item := range fis {
				if item.Name() == FileName {
					fi = item
					break
				}
			}
			if fi != nil {
				OutputBuffer, err = getFileInfo(fi, data.InfoLevel)
			}
			data.Header.Status = StatusOk
			if len(OutputBuffer) == 0 {
				return ERR(data.Header, STATUS_NO_SUCK_FILE)
			}
		}
	default:
		return ERR(data.Header, STATUS_NOT_SUPPORTED)
	}

	if err != nil {
		return nil, err
	}

	resp := QueryDirectoryResponse{
		Header:        data.Header,
		StructureSize: 0x0009,
		OutputBuffer:  OutputBuffer,
		// OutputBufferOffset: 0x0048,
		// OutputBufferLength: uint32(len(OutputBuffer)),
	}
	return &resp, nil
}
