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 {
	result, err := db.Exec(`
  INSERT INTO briefings
  	(date, time, location, document_name, as_of, instructor_id)
  VALUES
  	(?, ?, ?, ?, ?, ?)
  `, b.Date, b.Time, b.Location, b.DocumentName, b.AsOf, b.InstructorID)
	if err != nil {
		return fmt.Errorf("*DB.writeBriefing: db.Exec(): %v\n", err)
	}

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

	return nil
}

func (db *DB) writeParticipants(sp *[]*types.Participant) error {
	for _, p := range *sp {
		result, err := db.Exec(`
  	INSERT INTO participants
  		(first_name, last_name, company)
  	VALUES
  		(?, ?, ?)
  	`, p.FirstName, p.LastName, p.Company)
		if err != nil {
			return fmt.Errorf("*DB.writeParticipants: db.Exec(): %v\n", err)
		}

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

	return nil
}

func (db *DB) writeGivenAnswers(b *types.Briefing, sp *[]*types.Participant, sq *[]*types.Question, sg *[]*types.GivenAnswer) error {
	for _, p := range *sp {
		for i, q := range *sq {
			result, err := db.Exec(`
  		INSERT INTO given_answers
  			(briefing_id, participant_id, question_id, given_answer)
  		VALUES
  			(?, ?, ?, ?)
  		`, b.ID, p.ID, q.ID, (*sg)[i])
			if err != nil {
				return fmt.Errorf("*DB.writeGivenAnswers: db.Exec(): %v\n", err)
			}

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

	return nil
}

func (db *DB) WriteAllDataOfBriefing(b *types.Briefing, sp *[]*types.Participant, sq *[]*types.Question, sg *[]*types.GivenAnswer) error {
	if err := db.writeBriefing(b); err != nil {
		return fmt.Errorf("*DB.WriteAllDataOfBriefing: db.writeBriefing(): %v\n", err)
	}

	if err := db.writeParticipants(sp); err != nil {
		return fmt.Errorf("*DB.WriteAllDataOfBriefing: db.writeParticipants(): %v\n", err)
	}

	if err := db.writeGivenAnswers(b, sp, sq, sg); err != nil {
		return fmt.Errorf("*DB.WriteAllDataOfBriefing: db.writeGivenAnswers(): %v\n", err)
	}

	return nil
}

func (db *DB) GetAllOverviewTableData() (*[]*types.OverviewTableData, error) {
	rows, err := db.Query(`
  SELECT
    i.first_name,
    i.last_name,
    b.date,
    b.time,
    b.location,
    b.document_name,
    b.as_of,
    p.first_name,
    p.last_name,
    p.company
  FROM given_answers AS g
    INNER JOIN briefings AS b
      ON b.id = g.briefing_id
    INNER JOIN participants AS p
      ON p.id = g.participant_id
    INNER JOIN instructors AS i
      ON i.id = b.instructor_id
  ORDER BY
    b.id DESC,
    p.id
  `)
	if err != nil {
		return nil, fmt.Errorf("*DB.ReadAllBriefings: db.Query(): %v\n", err)
	}
	defer rows.Close()

	data := make([]*types.OverviewTableData, 0)
	for rows.Next() {
		otd := new(types.OverviewTableData)

		err := rows.Scan(
			&otd.InstructorFirstName,
			&otd.InstructorLastName,
			&otd.BriefingDate,
			&otd.BriefingTime,
			&otd.BriefingLocation,
			&otd.BriefingDocumentName,
			&otd.BriefingAsOf,
			&otd.ParticipantFirstName,
			&otd.ParticipantLastName,
			&otd.ParticipantCompany,
		)
		if err != nil {
			return nil, fmt.Errorf("*DB.ReadAllBriefings: rows.Scan(): %v\n", err)
		}

		data = append(data, otd)
	}

	return &data, nil
}

func (db *DB) GetOverviewTableDataByName(n string) (*[]*types.OverviewTableData, error) {
	rows, err := db.Query(`
  SELECT
    i.first_name,
    i.last_name,
    b.date,
    b.time,
    b.location,
    b.document_name,
    b.as_of,
    p.first_name,
    p.last_name,
    p.company
  FROM given_answers AS g
    INNER JOIN briefings AS b
      ON b.id = g.briefing_id
    INNER JOIN participants AS p
      ON p.id = g.participant_id
    INNER JOIN instructors AS i
      ON i.id = b.instructor_id
  WHERE
    i.first_name LIKE ? OR
    i.last_name LIKE ? OR
    p.first_name LIKE ? OR
    p.last_name LIKE ?
  ORDER BY
    b.id DESC,
    p.id
  `, "%"+n+"%", "%"+n+"%", "%"+n+"%", "%"+n+"%")
	if err != nil {
		return nil, fmt.Errorf("*DB.GetOverviewTableDataByName: db.Query(): %v\n", err)
	}
	defer rows.Close()

	data := make([]*types.OverviewTableData, 0)
	for rows.Next() {
		otd := new(types.OverviewTableData)

		err := rows.Scan(
			&otd.InstructorFirstName,
			&otd.InstructorLastName,
			&otd.BriefingDate,
			&otd.BriefingTime,
			&otd.BriefingLocation,
			&otd.BriefingDocumentName,
			&otd.BriefingAsOf,
			&otd.ParticipantFirstName,
			&otd.ParticipantLastName,
			&otd.ParticipantCompany,
		)
		if err != nil {
			return nil, fmt.Errorf("*DB.ReadAllBriefings: rows.Scan(): %v\n", err)
		}

		data = append(data, otd)
	}

	return &data, nil
}

// TODO: So nicht mehr nützlich, zeitnah streichen
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
}
