package towndb

import (
	"database/sql"
	"fmt"
	"time"

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

const dsn = "/town/var/town.db?mode=rw"

type UserState string

const (
	StateActive  = "active"
	StateTempBan = "temp_banned"
	StateBan     = "banned"
	StateDeleted = "deleted" // some users request deletion
)

type AdminNote struct {
	ID       int64
	Created  time.Time
	AuthorID int64
	Content  string
	UserID   int64
}

func (n *AdminNote) Insert(db *sql.DB) error {
	var (
		err    error
		stmt   *sql.Stmt
		result sql.Result
		liid   int64
	)

	tx, err := db.Begin()
	if err != nil {
		return err
	}

	defer func() {
		if err != nil {
			tx.Rollback()
		}
	}()
	stmt, err = tx.Prepare(`
		INSERT INTO notes (created, author, content)
		VALUES (?, ?, ?)`)
	if err != nil {
		return err
	}

	result, err = stmt.Exec(
		n.Created.Unix(),
		n.AuthorID,
		n.Content)
	if err != nil {
		return err
	}

	liid, err = result.LastInsertId()
	if err != nil {
		return err
	}

	n.ID = liid

	stmt, err = tx.Prepare(`
		INSERT INTO user_notes (noteid, userid) VALUES (?, ?)`)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(n.ID, n.UserID)
	if err != nil {
		return err
	}

	return tx.Commit()
}

type TownUser struct {
	ID       int64
	Created  time.Time
	Emails   []string
	Username string
	Notes    []AdminNote
	State    UserState
	IsAdmin  bool
}

func (u *TownUser) Insert(db *sql.DB) (err error) {
	var tx *sql.Tx
	var stmt *sql.Stmt
	var result sql.Result
	var liid int64

	if tx, err = db.Begin(); err != nil {
		return err
	}

	defer func() {
		if err != nil {
			tx.Rollback()
		}
	}()

	if stmt, err = tx.Prepare(`
		INSERT INTO users (created, username, state, admin)
		VALUES (?, ?, ?, ?)`); err != nil {
		return err
	}

	if result, err = stmt.Exec(
		u.Created.Unix(),
		u.Username,
		u.State,
		u.IsAdmin); err != nil {
		return err
	}

	if liid, err = result.LastInsertId(); err != nil {
		return err
	}

	u.ID = liid

	if len(u.Emails) > 0 {
		for _, e := range u.Emails {
			if stmt, err = tx.Prepare(`
				INSERT INTO emails (address, userid)
				VALUES (?, ?)`); err != nil {
				return err
			}
			if result, err = stmt.Exec(e, u.ID); err != nil {
				return err
			}
		}
	}

	return tx.Commit()
}

// TODO: really we should have like GetUser or something. but i don't want
// to have to populate the struct to do this operation for now ~equa
func RenameUser(db *sql.DB, old_name string, new_name string) (err error) {
	stmt, err := db.Prepare(`UPDATE users SET username = ? WHERE username = ?`)
	if err != nil {
		return err
	}

	result, err := stmt.Exec(new_name, old_name)
	if err != nil {
		return err
	}

	rows, err := result.RowsAffected()
	if err != nil {
		return err
	}

	if rows == 0 {
		return fmt.Errorf("couldn't find user")
	}

	return nil
}

func ConnectDB() (*sql.DB, error) {
	db, err := sql.Open("sqlite3", dsn)
	if err != nil {
		return nil, err
	}

	return db, nil
}
