package data

import (
	"bufio"
	"database/sql"
	"fmt"
	"os"
	"strings"
	"syscall"

	"streifling.com/jason/sicherheitsunterweisung/packages/types"

	"github.com/go-sql-driver/mysql"
	"golang.org/x/term"
)

type DB struct {
	*sql.DB
	Name string
}

func getUsername() (string, error) {
	user := os.Getenv("DB_USER")
	if user == "" {
		var err error
		fmt.Printf("DB Benutzer: ")
		user, err = bufio.NewReader(os.Stdin).ReadString('\n')
		if err != nil {
			return "", fmt.Errorf("getUsername: bufio.NewReader(os.Stdin).ReadString('\n'): %v", err)
		}
	}
	return strings.TrimSpace(user), nil
}

func getPassword() (string, error) {
	pass := os.Getenv("DB_PASS")
	if pass == "" {
		fmt.Printf("DB Passwort: ")
		bytePass, err := term.ReadPassword(int(syscall.Stdin))
		if err != nil {
			return "", fmt.Errorf("getCredentials: term.ReadPassword(int(syscall.Stdin)): %v", err)
		}
		fmt.Println()
		pass = strings.TrimSpace(string(bytePass))
	}
	return pass, nil
}

func getCredentials() (string, string, error) {
	user, err := getUsername()
	if err != nil {
		return "", "", fmt.Errorf("getCredentials: getUsername(): %v", err)
	}

	pass, err := getPassword()
	if err != nil {
		return "", "", fmt.Errorf("getCredentials: getPassword(): %v", err)
	}

	return user, pass, nil
}

func reverseOrder(bs []*types.Briefing) {
	for i, j := 0, len(bs)-1; i < j; i, j = i+1, j-1 {
		bs[i], bs[j] = bs[j], bs[i]
	}
}

func OpenDB(dbName string) (*DB, error) {
	var err error
	db := new(DB)

	cfg := mysql.NewConfig()
	cfg.DBName = dbName
	cfg.User, cfg.Passwd, err = getCredentials()
	if err != nil {
		return nil, fmt.Errorf("Open: getCredentials(): %v\n", err)
	}

	db.Name = dbName
	db.DB, err = sql.Open("mysql", cfg.FormatDSN())
	if err != nil {
		return nil, fmt.Errorf("Open: sql.Open(\"mysql\", cfg.FormatDSN()): %v\n", err)
	}
	if err := db.Ping(); err != nil {
		return nil, fmt.Errorf("Open: db.Ping(): %v\n", err)
	}

	return db, nil
}

func (db *DB) WriteBriefing(b *types.Briefing) error {
	for i := 0; i < len(b.Participants); i++ {
		result, err := db.Exec("INSERT INTO "+db.Name+" (instructor_first,"+
			" instructor_last, date, time, state, location, participant_first,"+
			" participant_last, company) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
			b.FirstName, b.LastName, b.Date, b.Time, b.State, b.Location,
			b.Participants[i].FirstName, b.Participants[i].LastName,
			b.Participants[i].Company)
		if err != nil {
			return fmt.Errorf("*DB.WriteBriefing: db.Exec(\"INSERT INTO"+
				" \"+db.Name+\" (instructor_first, instructor_last, date, time, state,"+
				" location, participant_first, participant_last, company) VALUES (?, ?,"+
				" ?, ?, ?, ?, ?, ?, ?)\", b.FirstName, b.LastName, b.Date, b.Time,"+
				" b.State, b.Location, b.Participants[i].FirstName,"+
				" b.Participants[i].LastName, b.Participants[i].Company): %v\n", err)
		}

		_, err = result.LastInsertId()
		if err != nil {
			return fmt.Errorf("*DB.WriteBriefing: result.LastInsertId(): %v\n", err)
		}
	}

	return nil
}

func (db *DB) ReadAllBriefings() ([]*types.Briefing, error) {
	bs := make([]*types.Briefing, 0)

	rows, err := db.Query("SELECT * FROM " + db.Name)
	if err != nil {
		return nil, fmt.Errorf("*DB.ReadAll: db.Query(\"SELECT * FROM \"+db.Name): %v\n", err)
	}
	defer rows.Close()

	for rows.Next() {
		b := new(types.Briefing)
		p := new(types.Participant)

		if err := rows.Scan(&p.ID, &b.FirstName, &b.LastName, &b.Date, &b.Time,
			&b.State, &b.Location, &p.FirstName, &p.LastName, &p.Company); err != nil {
			return nil, fmt.Errorf("*DB.ReadAll: db.Query(): %v\n", err)
		}

		b.Participants = append(b.Participants, p)
		bs = append(bs, b)
	}

	reverseOrder(bs)
	return bs, nil
}

func (db *DB) ReadByName(name string) ([]*types.Briefing, error) {
	bs := make([]*types.Briefing, 0)

	rows, err := db.Query("SELECT *"+
		" FROM "+db.Name+
		" WHERE instructor_first LIKE ?"+
		" OR instructor_last LIKE ?"+
		" OR participant_first LIKE ?"+
		" OR participant_last LIKE ?",
		"%"+name+"%", "%"+name+"%", "%"+name+"%", "%"+name+"%")
	if err != nil {
		return nil, fmt.Errorf("*DB.ReadByName: db.Query(\"SELECT *"+
			" FROM \"+db.Name+"+
			" WHERE instructor_first LIKE ?"+
			" OR instructor_last LIKE ?"+
			" OR participant_first LIKE ?"+
			" OR participant_last LIKE ?\"): %v\n", err)
	}
	defer rows.Close()

	for rows.Next() {
		b := new(types.Briefing)
		p := new(types.Participant)

		if err := rows.Scan(&p.ID, &b.FirstName, &b.LastName, &b.Date, &b.Time, &b.State,
			&b.Location, &p.FirstName, &p.LastName, &p.Company); err != nil {
			return nil, fmt.Errorf("*DB.ReadByName: rows.Scan(&p.ID, &b.FirstName,"+
				" &b.LastName, &b.Date, &b.Time, &b.State, &b.Location, &p.FirstName,"+
				" &p.LastName, &p.Company): %v\n", err)
		}
		b.Participants = append(b.Participants, p)
		bs = append(bs, b)
	}

	reverseOrder(bs)
	return bs, nil
}

func (db *DB) GetLastID(i *int64) error {
	row := db.QueryRow("SELECT id" +
		" FROM " + db.Name +
		" ORDER BY id DESC LIMIT 0, 1")

	if err := row.Scan(i); err != nil {
		return fmt.Errorf("*DB.GetLastID: row.Scan(&i): %v\n", err)
	}

	return nil
}
