package database

import (
	"errors"
	"fmt"
	"log"
	"net"
	"os"
	"sync"
	"time"

	"gordenko.dev/dima/qb"
	"gordenko.dev/dima/qb/inbox"
	"gordenko.dev/dima/qb/recovery"
	"gordenko.dev/dima/qb/storage"
	"gordenko.dev/dima/qb/worker"
)

// type metricLockEntry struct {
// 	XLock     bool
// 	RLocks    int
// 	WaitQueue []any
// }
//metricLockEntries map[uint32]*metricLockEntry

type Database struct {
	mutex        sync.Mutex
	dir          string
	databaseName string
	workerInbox  *inbox.Inbox
	worker       *worker.Worker
	storage      *storage.Writer
	tcpPort      int
	logfile      *os.File
	logger       *log.Logger
	exitCh       chan struct{}
	waitGroup    *sync.WaitGroup
}

type Options struct {
	TCPPort      int
	Dir          string
	DatabaseName string
	Logfile      *os.File
	ExitCh       chan struct{}
	WaitGroup    *sync.WaitGroup
}

func New(opt Options) (_ *Database, err error) {
	if opt.TCPPort <= 0 {
		return nil, errors.New("TCPPort option is required")
	}
	if opt.Dir == "" {
		return nil, errors.New("Dir option is required")
	}
	if opt.DatabaseName == "" {
		return nil, errors.New("DatabaseName option is required")
	}
	if opt.Logfile == nil {
		return nil, errors.New("Logfile option is required")
	}
	if opt.ExitCh == nil {
		return nil, errors.New("ExitCh option is required")
	}
	if opt.WaitGroup == nil {
		return nil, errors.New("WaitGroup option is required")
	}

	s := &Database{
		dir:          opt.Dir,
		databaseName: opt.DatabaseName,
		//metricLockEntries: make(map[uint32]*metricLockEntry),
		//dataFreeList:  dataFreeList,
		//indexFreeList: indexFreeList,
		tcpPort:   opt.TCPPort,
		logfile:   opt.Logfile,
		logger:    log.New(opt.Logfile, "", log.LstdFlags),
		exitCh:    opt.ExitCh,
		waitGroup: opt.WaitGroup,
	}

	recoveryReport, err := recovery.Recovery(s.dir, s.databaseName)
	if err != nil {
		return
	}

	storageInbox := inbox.New()
	s.workerInbox = inbox.New()

	s.storage, err = storage.NewWriter(storage.WriterOptions{
		Inbox:         storageInbox,
		WorkerInbox:   s.workerInbox,
		Dir:           s.dir,
		DatabaseName:  s.databaseName,
		WAL:           recoveryReport.WAL,
		IndexFreeList: recoveryReport.IndexFreeList,
		DataFreeList:  recoveryReport.DataFreeList,
		ExitCh:        s.exitCh,
		WaitGroup:     s.waitGroup,
	})
	if err != nil {
		qb.Abort(qb.CreateChangesWriterFailed, err)

	}

	s.worker = worker.NewWorker(worker.WorkerOptions{
		Inbox:        s.workerInbox,
		StorageInbox: storageInbox,
		//Metrics: make(map[uint32]*_metric), FIX
		Dir: opt.Dir,
		// SaveToStorage: func(in any) {
		// 	fmt.Println("save to storage")
		// },
		ExitCh:    opt.ExitCh,
		WaitGroup: opt.WaitGroup,
	})
	return s, nil
}

func (s *Database) ListenAndServe() (err error) {
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", s.tcpPort))
	if err != nil {
		return fmt.Errorf("net.Listen: %s; port=%d", err, s.tcpPort)
	}

	go s.storage.Run()

	// s.atree, err = atree.New(atree.Options{
	// 	Dir:          s.dir,
	// 	DatabaseName: s.databaseName,
	// })
	// if err != nil {
	// 	return fmt.Errorf("atree.New: %s", err)
	// }
	// s.atree.Run()

	s.waitGroup.Add(1)
	go s.worker.Run()

	s.logger.Println("database started")
	for {
		// Listen for an incoming connection.
		conn, err := listener.Accept()
		if err != nil {
			s.logger.Printf("listener.Accept: %s\n", err)
			time.Sleep(time.Second)
		} else {
			go s.handleTCPConn(conn)
		}
	}
}

//

// зробити object?

// func (s *Database) replayChanges(snapshotNumber int) (err error) {
// 	snapshot, err := readSnapshot(JoinSnapshotFileName(s.dir, snapshotNumber))
// 	if err != nil {
// 		return
// 	}

// 	return nil
// }

//func (s *Database) verifySnapshot(fileName string) (_ bool, err error) {
// 	file, err := os.Open(fileName)
// 	if err != nil {
// 		return
// 	}
// 	defer file.Close()

// 	stat, err := file.Stat()
// 	if err != nil {
// 		return
// 	}

// 	if stat.Size() <= 4 {
// 		return false, nil
// 	}

// 	var (
// 		payloadSize = stat.Size() - 4
// 		hash        = crc32.NewIEEE()
// 	)

// 	_, err = io.CopyN(hash, file, payloadSize)
// 	if err != nil {
// 		return
// 	}
// 	calculatedCRC := hash.Sum32()

// 	storedCRC, err := bin.ReadUint32(file)
// 	if err != nil {
// 		return
// 	}
// 	if storedCRC != calculatedCRC {
// 		return false, fmt.Errorf("strored CRC %d not equal calculated CRC %d",
// 			storedCRC, calculatedCRC)
// 	}
// 	return true, nil
// }
