package local

import (
	"crypto/sha256"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"sync"
	"time"

	"gitlink.org.cn/cloudream/common/pkgs/logger"
	"gitlink.org.cn/cloudream/common/utils/io2"
	clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
	stgglb "gitlink.org.cn/cloudream/jcs-pub/common/globals"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
)

const (
	TempDir   = "tmp"
	BlocksDir = "blocks"
)

type ShardStore struct {
	detail           *clitypes.UserSpaceDetail
	absRoot          string
	lock             sync.Mutex
	workingTempFiles map[string]bool
	done             chan any
}

func NewShardStore(root string, detail *clitypes.UserSpaceDetail) (*ShardStore, error) {
	absRoot, err := filepath.Abs(filepath.Join(root, detail.UserSpace.ShardStore.BaseDir))
	if err != nil {
		return nil, fmt.Errorf("get abs root: %w", err)
	}

	return &ShardStore{
		detail:           detail,
		absRoot:          absRoot,
		workingTempFiles: make(map[string]bool),
		done:             make(chan any, 1),
	}, nil
}

func (s *ShardStore) Start(ch *types.StorageEventChan) {
	s.getLogger().Infof("component start, root: %v, max size: %v", s.absRoot, s.detail.UserSpace.ShardStore.MaxSize)

	go func() {
		removeTempTicker := time.NewTicker(time.Minute * 10)
		defer removeTempTicker.Stop()

		for {
			select {
			case <-removeTempTicker.C:
				s.removeUnusedTempFiles()
			case <-s.done:
				return
			}
		}
	}()
}

func (s *ShardStore) removeUnusedTempFiles() {
	s.lock.Lock()
	defer s.lock.Unlock()

	log := s.getLogger()

	entries, err := os.ReadDir(filepath.Join(s.absRoot, TempDir))
	if err != nil {
		log.Warnf("read temp dir: %v", err)
		return
	}

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		if s.workingTempFiles[entry.Name()] {
			continue
		}

		info, err := entry.Info()
		if err != nil {
			log.Warnf("get temp file %v info: %v", entry.Name(), err)
			continue
		}

		path := filepath.Join(s.absRoot, TempDir, entry.Name())
		err = os.Remove(path)
		if err != nil {
			log.Warnf("remove temp file %v: %v", path, err)
		} else {
			log.Infof("remove unused temp file %v, size: %v, last mod time: %v", path, info.Size(), info.ModTime())
		}
	}
}

func (s *ShardStore) Stop() {
	s.getLogger().Infof("component stop")

	select {
	case s.done <- nil:
	default:
	}
}

func (s *ShardStore) Create(stream io.Reader) (types.FileInfo, error) {
	file, err := s.createTempFile()
	if err != nil {
		return types.FileInfo{}, err
	}

	counter := io2.Counter(stream)
	size, hash, err := s.writeTempFile(file, counter)
	if stgglb.Stats.HubStorageTransfer != nil {
		stgglb.Stats.HubStorageTransfer.RecordUpload(s.detail.Storage.StorageID, counter.Count(), err == nil)
	}
	if err != nil {
		// Name是文件完整路径
		s.onCreateFailed(file.Name())
		return types.FileInfo{}, err
	}

	return s.onCreateFinished(file.Name(), size, hash)
}

func (s *ShardStore) createTempFile() (*os.File, error) {
	s.lock.Lock()
	defer s.lock.Unlock()

	tmpDir := filepath.Join(s.absRoot, TempDir)

	err := os.MkdirAll(tmpDir, 0755)
	if err != nil {
		s.lock.Unlock()
		return nil, err
	}

	file, err := os.CreateTemp(tmpDir, "tmp-*")
	if err != nil {
		s.lock.Unlock()
		return nil, err
	}

	s.workingTempFiles[filepath.Base(file.Name())] = true

	return file, nil
}

func (s *ShardStore) writeTempFile(file *os.File, stream io.Reader) (int64, clitypes.FileHash, error) {
	defer file.Close()

	buf := make([]byte, 32*1024)
	size := int64(0)

	hasher := sha256.New()
	for {
		n, err := stream.Read(buf)
		if n > 0 {
			size += int64(n)
			io2.WriteAll(hasher, buf[:n])
			err := io2.WriteAll(file, buf[:n])
			if err != nil {
				return 0, "", err
			}
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			return 0, "", err
		}
	}

	h := hasher.Sum(nil)
	return size, clitypes.NewFullHash(h), nil
}

func (s *ShardStore) onCreateFinished(tempFilePath string, size int64, hash clitypes.FileHash) (types.FileInfo, error) {
	s.lock.Lock()
	defer s.lock.Unlock()
	defer delete(s.workingTempFiles, filepath.Base(tempFilePath))

	log := s.getLogger()

	log.Debugf("write file %v finished, size: %v, hash: %v", tempFilePath, size, hash)

	blockDir := s.getFileDirFromHash(hash)
	err := os.MkdirAll(blockDir, 0755)
	if err != nil {
		s.removeTempFile(tempFilePath)
		log.Warnf("make block dir %v: %v", blockDir, err)
		return types.FileInfo{}, fmt.Errorf("making block dir: %w", err)
	}

	newPath := filepath.Join(blockDir, string(hash))
	_, err = os.Stat(newPath)
	if os.IsNotExist(err) {
		err = os.Rename(tempFilePath, newPath)
		if err != nil {
			s.removeTempFile(tempFilePath)
			log.Warnf("rename %v to %v: %v", tempFilePath, newPath, err)
			return types.FileInfo{}, fmt.Errorf("rename file: %w", err)
		}

	} else if err != nil {
		s.removeTempFile(tempFilePath)
		log.Warnf("get file %v stat: %v", newPath, err)
		return types.FileInfo{}, fmt.Errorf("get file stat: %w", err)
	} else {
		s.removeTempFile(tempFilePath)
	}

	return types.FileInfo{
		Hash:        hash,
		Size:        size,
		Description: newPath,
	}, nil
}

func (s *ShardStore) onCreateFailed(tempFilePath string) {
	s.lock.Lock()
	defer s.lock.Unlock()

	s.getLogger().Debugf("writting file %v aborted", tempFilePath)
	s.removeTempFile(tempFilePath)
	delete(s.workingTempFiles, filepath.Base(tempFilePath))
}

func (s *ShardStore) removeTempFile(path string) {
	err := os.Remove(path)
	if err != nil {
		s.getLogger().Warnf("removing temp file %v: %v", path, err)
	}
}

// 使用NewOpen函数创建Option对象
func (s *ShardStore) Open(opt types.OpenOption) (io.ReadCloser, error) {
	s.lock.Lock()
	defer s.lock.Unlock()

	filePath := s.getFilePathFromHash(opt.FileHash)
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}

	if opt.Offset > 0 {
		_, err = file.Seek(opt.Offset, io.SeekStart)
		if err != nil {
			file.Close()
			return nil, err
		}
	}

	var ret io.ReadCloser = file

	if opt.Length >= 0 {
		ret = io2.Length(ret, opt.Length)
	}

	return io2.CounterCloser(ret, func(cnt int64, err error) {
		if stgglb.Stats.HubStorageTransfer != nil {
			stgglb.Stats.HubStorageTransfer.RecordDownload(s.detail.Storage.StorageID, cnt, err == nil || err == io.EOF)
		}
	}), nil
}

func (s *ShardStore) Info(hash clitypes.FileHash) (types.FileInfo, error) {
	s.lock.Lock()
	defer s.lock.Unlock()

	filePath := s.getFilePathFromHash(hash)
	info, err := os.Stat(filePath)
	if err != nil {
		return types.FileInfo{}, err
	}

	return types.FileInfo{
		Hash:        hash,
		Size:        info.Size(),
		Description: filePath,
	}, nil
}

func (s *ShardStore) ListAll() ([]types.FileInfo, error) {
	s.lock.Lock()
	defer s.lock.Unlock()

	var infos []types.FileInfo

	blockDir := filepath.Join(s.absRoot, BlocksDir)
	err := filepath.WalkDir(blockDir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		if d.IsDir() {
			return nil
		}

		info, err := d.Info()
		if err != nil {
			return err
		}

		fileHash, err := clitypes.ParseHash(filepath.Base(info.Name()))
		if err != nil {
			return nil
		}

		infos = append(infos, types.FileInfo{
			Hash:        fileHash,
			Size:        info.Size(),
			Description: filepath.Join(blockDir, path),
		})
		return nil
	})
	if err != nil && !errors.Is(err, os.ErrNotExist) {
		return nil, err
	}

	return infos, nil
}

func (s *ShardStore) GC(avaiables []clitypes.FileHash) error {
	s.lock.Lock()
	defer s.lock.Unlock()

	avais := make(map[clitypes.FileHash]bool)
	for _, hash := range avaiables {
		avais[hash] = true
	}

	cnt := 0

	blockDir := filepath.Join(s.absRoot, BlocksDir)
	err := filepath.WalkDir(blockDir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		if d.IsDir() {
			return nil
		}

		info, err := d.Info()
		if err != nil {
			return err
		}

		fileHash, err := clitypes.ParseHash(filepath.Base(info.Name()))
		if err != nil {
			return nil
		}

		if !avais[fileHash] {
			err = os.Remove(path)
			if err != nil {
				s.getLogger().Warnf("remove file %v: %v", path, err)
			} else {
				cnt++
			}
		}

		return nil
	})
	if err != nil && !errors.Is(err, os.ErrNotExist) {
		return err
	}

	s.getLogger().Infof("purge %d files", cnt)

	// TODO 无法保证原子性，所以删除失败只打日志
	return nil
}

func (s *ShardStore) Stats() types.Stats {
	// TODO 统计本地存储的相关信息
	return types.Stats{
		Status: types.StatusOK,
	}
}

func (s *ShardStore) getLogger() logger.Logger {
	return logger.WithField("ShardStore", "Local").WithField("Storage", s.detail.Storage.String())
}

func (s *ShardStore) getFileDirFromHash(hash clitypes.FileHash) string {
	return filepath.Join(s.absRoot, BlocksDir, hash.GetHashPrefix(2))
}

func (s *ShardStore) getFilePathFromHash(hash clitypes.FileHash) string {
	return filepath.Join(s.absRoot, BlocksDir, hash.GetHashPrefix(2), string(hash))
}

var _ types.BypassShardWrite = (*ShardStore)(nil)

func (s *ShardStore) BypassedShard(info types.BypassedFileInfo) error {
	s.lock.Lock()
	defer s.lock.Unlock()

	log := s.getLogger()

	log.Debugf("%v bypass uploaded, size: %v, hash: %v", info.Path, info.Size, info.Hash)

	blockDir := s.getFileDirFromHash(info.Hash)
	err := os.MkdirAll(blockDir, 0755)
	if err != nil {
		log.Warnf("make block dir %v: %v", blockDir, err)
		return fmt.Errorf("making block dir: %w", err)
	}

	newPath := filepath.Join(blockDir, string(info.Hash))
	_, err = os.Stat(newPath)
	if os.IsNotExist(err) {
		err = os.Rename(info.Path, newPath)
		if err != nil {
			log.Warnf("rename %v to %v: %v", info.Path, newPath, err)
			return fmt.Errorf("rename file: %w", err)
		}

	} else if err != nil {
		log.Warnf("get file %v stat: %v", newPath, err)
		return fmt.Errorf("get file stat: %w", err)
	}

	return nil
}

var _ types.BypassShardRead = (*ShardStore)(nil)

func (s *ShardStore) BypassShardRead(fileHash clitypes.FileHash) (types.BypassFilePath, error) {
	s.lock.Lock()
	defer s.lock.Unlock()

	filePath := s.getFilePathFromHash(fileHash)
	stat, err := os.Stat(filePath)
	if err != nil {
		return types.BypassFilePath{}, err
	}

	return types.BypassFilePath{
		Path: filePath,
		Info: types.FileInfo{
			Hash:        fileHash,
			Size:        stat.Size(),
			Description: filePath,
		},
	}, nil
}
