package towndb

import (
	"database/sql"
	"errors"
	"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()
}

// UserForEmail returns the user associated with an email or nil if no matching user is found
func UserForEmail(db *sql.DB, address string) (*TownUser, error) {
	stmt, err := db.Prepare(`
		SELECT u.id, u.username FROM users u
		JOIN emails e ON e.userid = u.id
		WHERE e.address = ?
	`)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	row := stmt.QueryRow(address)
	u := &TownUser{}
	if err = row.Scan(&u.ID, &u.Username); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, nil
		}
		return nil, err
	}

	return u, nil
}

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

	return db, nil
}
