package main

import (
	"database/sql"
	"flag"
	"log"

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

func showTransactionStmtClose(p *sql.Stmt) {
	log.Println("Closing statement!")
	p.Close()
}

func main() {

	log.Println("Hello, world!")
	db_path := flag.String("database", "./data/trysqlite.db", "Path to a Sqlite3 database")
	flag.Parse()

	// TODO figure out what query string options to be using
	full_database_path := "file:" + *db_path + "?cache=shared"

	log.Printf("Using Database: %s", full_database_path)
	// db is a Handle backed by a go database/sql connection pool. It is not a connection in of itself, nor is it the actual connection pool
	// The first connection is made lazily by the handle, and this doesn't validate any of the connection parameters either at this point.
	// It simply prepares the abstraction for use.

	db, err := sql.Open("sqlite3", full_database_path)
	if err != nil {
		log.Fatalf("Failed to connect to sqlite database: %s", full_database_path)
	}
	// It is idiomatic to defer db.Close() if the sql.DB should not have a lifetime beyond the scope of the function.
	//Although it’s idiomatic to Close() the database when you’re finished with it, the sql.DB object is designed to be long-lived.
	//Don’t Open() and Close() databases frequently. Instead, create one sql.DB object for each distinct datastore you need to access,
	// and keep it until the program is done accessing that datastore.

	// Pass it around as needed, or make it available somehow globally, but keep it open.
	// And don’t Open() and Close() from a short-lived function. Instead, pass the sql.DB
	// into that short-lived function as an argument.

	//If you don’t treat the sql.DB as a long-lived object, you could experience problems such as poor reuse and sharing of connections,
	// running out of available network resources, or sporadic failures due to a lot of TCP connections remaining in TIME_WAIT status.
	// Such problems are signs that you’re not using database/sql as it was designed.
	defer db.Close()

	// If you want to check right away that the db is available and accessible you can do this and check for errs
	err = db.Ping()
	if err != nil {
		log.Fatal("DB Ping failed. Check database and database connection parameters")
	}

	var (
		id      int
		subject string
		todo    string
	)

	// db.Query vs db.Exec. If a function name includes Query, it is designed to ask a question of the database,
	// and will return a set of rows, even if it’s empty. Statements that don’t return rows should not use Query functions;
	// they should use Exec()

	rows, err := db.Query("SELECT subject FROM todos")
	if err != nil {
		log.Fatal(err)

	}
	// It is important to defer closing a row object also. Why? It will release the memory and the network connection. So it
	// also prevents resource leaks. So you are a good steward, you clean up after yourself.
	// You don't wait for the garbage collector to do it.

	// as long as there’s an open result set (represented by rows), the underlying connection is busy and can’t be used for
	//any other query. It's busy because sql.Rows objects don't retrieve all results a query returns. It does it lazily to prevent over runing memory.
	// it depends on the driver but there is usually and internal buffer that stores results.

	// Also never defer in a loop. Defer only runs on function exit.
	defer rows.Close()
	// Iterate through each. Internally rows.Next will hit an EOF error it will call rows.close() for you freeing up the connection resources.
	// but you shouldn't rely on that alone. rows.Close() will run on function exit, but this example is a Main func, so it's not realistic
	// still get in the habit
	for rows.Next() {
		err = rows.Scan(&subject)
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Subject is %s", subject)
	}

	//  Don’t just assume that the loop iterates until you’ve processed all the rows. Always check.
	err = rows.Err()
	if err != nil {
		log.Fatal(err)
	}

	// You could also clean up at this point. The call is supposed to be idempotent, but defer is more idiomatic.
	// You don't want to call close if there was an Error because of that could cause a Panic.
	rows.Close()

	// If never want to do string concat from user input into a sql statement you run unless you like sql injection attacks
	// expects to return one row. If no row it will delay throwing error until Scan is called
	row := db.QueryRow("SELECT id, subject, todo FROM todos WHERE id = ?", 1)
	// Scan does the heavily lifting for you when casting from db type to go type. It will cast based on the variable type.
	// failures of course will be returned as part of the err
	err = row.Scan(&id, &subject, &todo)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("id: %d, subject: %s, todo: %s", id, subject, todo)

	// This should error on scan
	// We can also use this short hand!
	err = db.QueryRow("SELECT * FROM todos WHERE id = ?", 100_000).Scan(&id, &subject, &todo)
	if err != nil {
		log.Print(err.Error())
	}

	// There is no row.Close(). Rows objects have a Close() method. No explicit closing required.

	//Prepared Queries
	// If you will be using the same query over and over you should just prepare the query.
	// $N (e.g. $1, $2, etc...) is the postgres query param which SQLite can use too ? is mysql's

	//Under the hood, db.Query() actually prepares, executes, and closes a prepared statement
	// That’s three round-trips to the database.
	stmt, err := db.Prepare("SELECT todo as other_todo FROM todos WHERE id = $1")
	if err != nil {
		log.Fatal(err)
	}

	//This prepared statement *sql.Stmt is tied to the database connection from which it was created, and it holds onto that connection until you call stmt.Close()
	defer stmt.Close()

	// You can also call with just params the .QueryRow method since the statement is already prepared
	var other_todo string

	err = stmt.QueryRow(1).Scan(&other_todo)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Other Todo: %s", other_todo)
	stmt.Close()

	// MODIFYING DATA
	// Use db.Exec() with a prepared statement when INSERTING, UPDATING, or DELETING
	// Or another statement that doesn't return rows.

	stmt, err = db.Prepare("INSERT INTO todos (subject, todo) VALUES ($1, $2)")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()

	// stmt.Exec returns a sql.Result. That also has access to the statement metadata
	// use _ if you don't care about the result and only want to check the err.

	// Exec will NOT reserve a connection. unlike db.Query which returns a sql.Rows that
	// will hold on to a connection until .Close() is called.
	res, err := stmt.Exec("programming", "learn golang database queries!")
	if err != nil {
		log.Print("Dirp...")
		log.Fatal(err)
	}

	lastId, err := res.LastInsertId()
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Last inserted id: %d", lastId)

	rowCnt, err := res.RowsAffected()
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("The number of rows affected: %d", rowCnt)
	// close insert
	stmt.Close()

	stmt, err = db.Prepare("DELETE FROM todos WHERE subject = 'programming'")
	if err != nil {
		log.Fatal(err)
	}

	res, err = stmt.Exec()
	if err != nil {
		log.Fatal(err)
	}

	rowCnt, err = res.RowsAffected()
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("The number of rows affected: %d", rowCnt)

	// TRANSACTIONS

	//In Go, a transaction is essentially an object that reserves a connection to the datastore.
	//It lets you do all of the operations we’ve seen thus far, but guarantees that they’ll be
	//executed on the same connection.

	// Starts with db.Begin() returning a sql.Tx and ends with a .Commit() or .Rollback() on the results sql.Tx

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

	// Prepared statement in a transaction are bound to that transaction exclusively.

	// DO NOT use sql BEGIN or COMMIT in a sql.Tx object. It could leave the DB and client in different states.
	// or mess havoc with the connection pool and connection lifetime
	insert_stmt, err := tx.Prepare("INSERT INTO todos (subject, todo) VALUES ($1, $2)")
	if err != nil {
		log.Fatal(err)
	}
	defer insert_stmt.Close() // This closes at the end of the transaction, not the end of the function.

	_, _ = insert_stmt.Exec("dirp", "just a place holder")

	// There is no Close method on a transaction. It is closed by using .Commit or .Rollback
	tx.Rollback()

	// PREPARED STATEMENTS IN MORE DEPTH

	//At the database level, a prepared statement is bound to a single database connection.
	//The typical flow is that the client sends a SQL statement with placeholders to the server
	//for preparation, the server responds with a statement ID, and then the client executes
	//the statement by sending its ID and parameters.

	// THE ABOVE DOES NOT HAPPEN IN database/sql. Instead the statement is managed at the driver level
	// Go doesn't expose connections directly to users. The sql.Stmt object remembers which connection
	// from the pool it is associated with.

	//Know that if a connection is busy it may automatically get a new connection and re-prepare the
	// statement on a new connection. It could cause more connections than you think, more statement
	// recreation, or even hitting a server limit on prepared statements.

	// NOTE db.Query(sql, param1, param2) will create a prepared statement under the hood.
	// If you don’t want to use a prepared statement, you need to use fmt.Sprint() or similar to
	//assemble the SQL, and pass this as the only argument to db.Query() or db.QueryRow() And your driver
	//needs to support plaintext query execution,

	// NOTE prepared statements on Tx are bound exclusively to the Tx. You cannot use a prepared statement
	// created by db.Prepare nor can you use a Tx.Prepare statement with a db.Query. Tx.Stmt() can be used
	// to copy a db.Prepared statement into a transaction, but don't do this.

	// HANDLING ERRORS

	// You should always explicitly close a sql.Rows result set. If rows.Close() returns an error, it’s
	// unclear what you should do, so logging the error message and ignoring or panic-ing might be the only sensible
	// thing to do.

	// Use sql.ErrNoRows in the event that no data is returned.
	// rememeber db.QueryRow defers errors till Scan is called.
	err = db.QueryRow("select subject from todos where id = $1", 100_000).Scan(&subject)
	if err != nil {
		if err == sql.ErrNoRows {
			// there were no rows, but otherwise no error occurred
			log.Println("No rows were returned! But that is okay. We expected that could happen")
		} else {
			log.Fatal(err)
		}
	}

	// Each driver has it's own error types

}
