package holder

import (
	"os"
	"strings"
	"sync"
	"sync/atomic"
)

import (
	"vimagination.zapto.org/byteio"
)

import (
	"github.com/transaction-wg/seata-golang/pkg/tc/model"
	"github.com/transaction-wg/seata-golang/pkg/tc/session"
	"github.com/transaction-wg/seata-golang/pkg/util/log"
	"github.com/transaction-wg/seata-golang/pkg/util/time"
)

var FileTrxNum int64 = 0
var PerFileBlockSize int64 = 65535 * 8
var HisDataFilenamePostfix = ".1"
var MaxTrxTimeoutMills int64 = 30 * 60 * 1000

type ReloadableStore interface {
	/**
	 * Read write holder.
	 *
	 * @param readSize  the read size
	 * @param isHistory the is history
	 * @return the list
	 */
	ReadWriteStore(readSize int, isHistory bool) []*TransactionWriteStore

	/**
	 * Has remaining boolean.
	 *
	 * @param isHistory the is history
	 * @return the boolean
	 */
	HasRemaining(isHistory bool) bool
}

type FileTransactionStoreManager struct {
	SessionManager SessionManager

	currFullFileName  string
	hisFullFileName   string
	currFileChannel   *os.File
	LastModifiedTime  int64
	TrxStartTimeMills int64
	sync.Mutex

	recoverHisOffset  int64
	recoverCurrOffset int64
}

func (storeManager *FileTransactionStoreManager) InitFile(fullFileName string) {
	storeManager.currFullFileName = fullFileName
	storeManager.hisFullFileName = fullFileName + HisDataFilenamePostfix
	storeManager.currFileChannel, _ = os.OpenFile(fullFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777)
	storeManager.TrxStartTimeMills = int64(time.CurrentTimeMillis())
}

func (storeManager *FileTransactionStoreManager) writeDataFrame(data []byte) {
	dataLength := uint32(len(data))
	dataLengthBytes := [4]byte{
		byte(dataLength >> 24),
		byte(dataLength >> 16),
		byte(dataLength >> 8),
		byte(dataLength),
	}
	storeManager.currFileChannel.Write(dataLengthBytes[:4])
	storeManager.currFileChannel.Write(data)
}

func (storeManager *FileTransactionStoreManager) WriteSession(logOperation LogOperation, session session.SessionStorable) bool {
	storeManager.Lock()
	defer storeManager.Unlock()
	var curFileTrxNum int64 = 0
	data, err := (&TransactionWriteStore{
		SessionRequest: session,
		LogOperation:   logOperation,
	}).Encode()
	if err != nil {
		log.Info(err.Error())
		return false
	}
	storeManager.writeDataFrame(data)
	storeManager.LastModifiedTime = int64(time.CurrentTimeMillis())
	curFileTrxNum = atomic.AddInt64(&FileTrxNum, 1)
	if curFileTrxNum%PerFileBlockSize == 0 &&
		int64(time.CurrentTimeMillis())-storeManager.TrxStartTimeMills > MaxTrxTimeoutMills {
		storeManager.saveHistory()
	}
	return true
}

func (storeManager *FileTransactionStoreManager) ReadSession(xid string) *session.GlobalSession {
	return nil
}

func (storeManager *FileTransactionStoreManager) ReadSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession {
	return nil
}

func (storeManager *FileTransactionStoreManager) ReadSessionWithSessionCondition(sessionCondition model.SessionCondition) []*session.GlobalSession {
	return nil
}

func (storeManager *FileTransactionStoreManager) Shutdown() {
	storeManager.currFileChannel.Close()
}


func (storeManager *FileTransactionStoreManager) saveHistory() {
	storeManager.findTimeoutAndSave()
	os.Rename(storeManager.currFullFileName, storeManager.hisFullFileName)
	storeManager.InitFile(storeManager.currFullFileName)
}

func (storeManager *FileTransactionStoreManager) findTimeoutAndSave() (bool, error) {
	globalSessionsOverMaxTimeout := storeManager.SessionManager.FindGlobalSessions(model.SessionCondition{OverTimeAliveMills: MaxTrxTimeoutMills})

	if globalSessionsOverMaxTimeout == nil {
		return true, nil
	}

	for _, globalSession := range globalSessionsOverMaxTimeout {
		globalWriteStore := &TransactionWriteStore{
			SessionRequest: globalSession,
			LogOperation:   LogOperationGlobalAdd,
		}
		data, err := globalWriteStore.Encode()
		if err != nil {
			return false, err
		}
		storeManager.writeDataFrame(data)

		branchSessIonsOverMaXTimeout := globalSession.GetSortedBranches()
		if len(branchSessIonsOverMaXTimeout) > 0 {
			for _, branchSession := range branchSessIonsOverMaXTimeout {
				branchWriteStore := &TransactionWriteStore{
					SessionRequest: branchSession,
					LogOperation:   LogOperationBranchAdd,
				}
				data, err := branchWriteStore.Encode()
				if err != nil {
					return false, err
				}
				storeManager.writeDataFrame(data)
			}
		}
	}
	return true, nil
}

func (storeManager *FileTransactionStoreManager) ReadWriteStore(readSize int, isHistory bool) []*TransactionWriteStore {
	var (
		file          *os.File
		currentOffset int64
	)
	if isHistory {
		file, _ = os.OpenFile(storeManager.hisFullFileName, os.O_RDWR|os.O_CREATE, 0777)
		currentOffset = storeManager.recoverHisOffset
	} else {
		file, _ = os.OpenFile(storeManager.currFullFileName, os.O_RDWR|os.O_CREATE, 0777)
		currentOffset = storeManager.recoverCurrOffset
	}

	return storeManager.parseDataFile(file, readSize, currentOffset)
}

func (storeManager *FileTransactionStoreManager) HasRemaining(isHistory bool) bool {
	var (
		file          *os.File
		currentOffset int64
	)
	if isHistory {
		file, _ = os.OpenFile(storeManager.hisFullFileName, os.O_RDWR|os.O_CREATE, 0777)
		currentOffset = storeManager.recoverHisOffset
	} else {
		file, _ = os.OpenFile(storeManager.currFullFileName, os.O_RDWR|os.O_CREATE, 0777)
		currentOffset = storeManager.recoverCurrOffset
	}
	defer file.Close()

	fi, _ := file.Stat()
	return currentOffset < fi.Size()
}

func (storeManager *FileTransactionStoreManager) parseDataFile(file *os.File, readSize int, currentOffset int64) []*TransactionWriteStore {
	defer file.Close()
	var result = make([]*TransactionWriteStore, 0)
	fi, _ := file.Stat()
	fileSize := fi.Size()
	reader := byteio.BigEndianReader{Reader: file}
	offset := currentOffset
	for offset < fileSize {
		file.Seek(offset, 0)
		dataLength, n, _ := reader.ReadUint32()
		if n < 4 {
			break
		}

		data := make([]byte, int(dataLength))
		length, _ := reader.Read(data)
		offset += int64(length + 4)

		if length == int(dataLength) {
			st := &TransactionWriteStore{}
			st.Decode(data)
			result = append(result, st)
			if len(result) == readSize {
				break
			}
		} else if length == 0 {
			break
		}
	}
	if isHisFile(fi.Name()) {
		storeManager.recoverHisOffset = offset
	} else {
		storeManager.recoverCurrOffset = offset
	}
	return result
}

func isHisFile(path string) bool {
	return strings.HasSuffix(path, HisDataFilenamePostfix)
}
