package database

import (
	"embed"
	"os"
	"path/filepath"
	"time"

	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"
)

// Interface for the database
type Database interface {
	// Insert a new user into the database, password should be hashed before calling
	AddUser(username string, password string) error

	RemoveUser(username string) error
	PromoteToAdmin(username string) error
	GetUserId(username string) (int, error)
	AddProject(name string, description string, username string) error
	Migrate(dirname string) error
	// AddTimeReport(projectname string, start time.Time, end time.Time) error
	// AddUserToProject(username string, projectname string) error
	// ChangeUserRole(username string, projectname string, role string) error
	// AddTimeReport(projectname string, start time.Time, end time.Time) error
	// AddUserToProject(username string, projectname string) error
	// ChangeUserRole(username string, projectname string, role string) error
}

// This struct is a wrapper type that holds the database connection
// Internally DB holds a connection pool, so it's safe for concurrent use
type Db struct {
	*sqlx.DB
}

//go:embed migrations
var scripts embed.FS

const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?"
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
const addTimeReport = "INSERT INTO activity (report_id, activity_nbr, start_time, end_time, break, comment) VALUES (?, ?, ?, ?, ?, ?)" // WIP
const addUserToProject = "INSERT INTO project_member (project_id, user_id, role) VALUES (?, ?, ?)"                                     // WIP
// const changeUserRole = ""

// DbConnect connects to the database
func DbConnect(dbpath string) Database {
	// Open the database
	db, err := sqlx.Connect("sqlite3", dbpath)
	if err != nil {
		panic(err)
	}

	// Ping forces the connection to be established
	err = db.Ping()
	if err != nil {
		panic(err)
	}

	return &Db{db}
}

func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time, breakTime uint32) error { // WIP
	_, err := d.Exec(addTimeReport, projectname, 0, start, end, breakTime, false)
	return err
}

func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP
	var userid int
	userid, err := d.GetUserId(username)
	if err != nil {
		panic(err)
	}

	var projectid int
	projectid, err2 := d.GetProjectId(projectname)
	if err2 != nil {
		panic(err2)
	}

	_, err3 := d.Exec(addUserToProject, projectid, userid, role)
	return err3
}

// func (d *Db) ChangeUserRole(username string, projectname string, role string) error {

// }

// AddUser adds a user to the database
func (d *Db) AddUser(username string, password string) error {
	_, err := d.Exec(userInsert, username, password)
	return err
}

// Removes a user from the database
func (d *Db) RemoveUser(username string) error {
	_, err := d.Exec("DELETE FROM users WHERE username = ?", username)
	return err
}

func (d *Db) PromoteToAdmin(username string) error {
	_, err := d.Exec(promoteToAdmin, username)
	return err
}

func (d *Db) GetUserId(username string) (int, error) {
	var id int
	err := d.Get(&id, "SELECT id FROM users WHERE username = ?", username) // Borde det inte vara "user" i singular
	return id, err
}

func (d *Db) GetProjectId(projectname string) (int, error) { // WIP, denna kan vara goof
	var id int
	err := d.Get(&id, "SELECT id FROM project WHERE project_name = ?", projectname)
	return id, err
}

// Creates a new project in the database, associated with a user
func (d *Db) AddProject(name string, description string, username string) error {
	_, err := d.Exec(projectInsert, name, description, username)
	return err
}

// Reads a directory of migration files and applies them to the database.
// This will eventually be used on an embedded directory
func (d *Db) Migrate(dirname string) error {
	// Read the embedded scripts directory
	files, err := scripts.ReadDir("migrations")
	if err != nil {
		return err
	}

	tr := d.MustBegin()

	// Iterate over each SQL file and execute it
	for _, file := range files {
		if file.IsDir() || filepath.Ext(file.Name()) != ".sql" {
			continue
		}

		// This is perhaps not the most elegant way to do this
		sqlFile := filepath.Join("migrations", file.Name())
		sqlBytes, err := os.ReadFile(sqlFile)
		if err != nil {
			return err
		}

		sqlQuery := string(sqlBytes)
		_, err = tr.Exec(sqlQuery)
		if err != nil {
			return err
		}
	}

	if tr.Commit() != nil {
		return err
	}

	return nil
}