package replica

import (
	"database/sql"
	"encoding/json"
	"os"

	"github.com/go-mysql-org/go-mysql/mysql"
)

// PositionManager handles binlog position persistence
type PositionManager struct {
	db           *sql.DB
	positionFile string
}

// NewPositionManager creates a new position manager
func NewPositionManager(db *sql.DB, positionFile string) *PositionManager {
	return &PositionManager{
		db:           db,
		positionFile: positionFile,
	}
}

// InitTable creates the binlog_position table if it doesn't exist
func (pm *PositionManager) InitTable() error {
	if pm.db == nil {
		return nil
	}

	createTableSQL := `
		CREATE TABLE IF NOT EXISTS binlog_position (
			id INT PRIMARY KEY,
			log_file VARCHAR(255),
			log_pos BIGINT,
			updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
		)
	`

	_, err := pm.db.Exec(createTableSQL)
	if err != nil {
		Errorf("[ERROR] Failed to create binlog_position table: %v", err)
		return err
	}

	return nil
}

// Load loads the last saved position from database or file
func (pm *PositionManager) Load() (mysql.Position, error) {
	if pm.db != nil {
		return pm.loadFromDB()
	}
	return pm.loadFromFile()
}

func (pm *PositionManager) loadFromDB() (mysql.Position, error) {
	var name string
	var pos uint32

	err := pm.db.QueryRow(
		"SELECT log_file, log_pos FROM binlog_position WHERE id = 1",
	).Scan(&name, &pos)

	if err == sql.ErrNoRows {
		return mysql.Position{Name: "", Pos: 0}, nil
	}
	if err != nil {
		return mysql.Position{Name: "", Pos: 0}, err
	}

	Infof("[INFO] Loaded position: %s:%d", name, pos)
	return mysql.Position{Name: name, Pos: pos}, nil
}

func (pm *PositionManager) loadFromFile() (mysql.Position, error) {
	data, err := os.ReadFile(pm.positionFile)
	if err != nil {
		return mysql.Position{Name: "", Pos: 0}, nil
	}

	var pos mysql.Position
	err = json.Unmarshal(data, &pos)
	return pos, err
}

// Save saves the current position to database or file
func (pm *PositionManager) Save(pos mysql.Position) error {
	if pm.db != nil {
		return pm.saveToDB(pos)
	}
	return pm.saveToFile(pos)
}

// Reset clears the saved position
func (pm *PositionManager) Reset() error {
	if pm.db != nil {
		return pm.resetInDB()
	}
	return pm.resetInFile()
}

func (pm *PositionManager) resetInDB() error {
	_, err := pm.db.Exec("DELETE FROM binlog_position WHERE id = 1")
	return err
}

func (pm *PositionManager) resetInFile() error {
	return os.Remove(pm.positionFile)
}

func (pm *PositionManager) saveToDB(pos mysql.Position) error {
	result, err := pm.db.Exec(
		"INSERT INTO binlog_position (id, log_file, log_pos) VALUES (1, ?, ?) "+
			"ON DUPLICATE KEY UPDATE log_file = VALUES(log_file), log_pos = VALUES(log_pos)",
		pos.Name, pos.Pos,
	)

	if err != nil {
		Errorf("[ERROR] Failed to save position: %v", err)
		return err
	}

	rowsAffected, _ := result.RowsAffected()
	Infof("[INFO] Saved position: %s:%d (rows: %d)", pos.Name, pos.Pos, rowsAffected)
	return nil
}

func (pm *PositionManager) saveToFile(pos mysql.Position) error {
	data, err := json.Marshal(pos)
	if err != nil {
		return err
	}
	return os.WriteFile(pm.positionFile, data, 0644)
}

// GetPositionFile returns the position file path
func (pm *PositionManager) GetPositionFile() string {
	return pm.positionFile
}
