package sql

// |@@| C

import (
	"context"
	"database/sql"
	"fmt"
	"github.com/mattn/go-sqlite3"
)

type SqliteMemorySqlIpl struct {
}

func (this *SqliteMemorySqlIpl) Kind() string {
	return "sqlite-memory"
}

func (this *SqliteMemorySqlIpl) Driver() string {
	return DRIVER_SQLITE3
}

func (this *SqliteMemorySqlIpl) DataSourceName(ctx context.Context, rootDir string) string {
	return "file::memory:"
}

func (this *SqliteMemorySqlIpl) SchemaPath(ctx context.Context, rootDir string) string {
	return rootDir + "/data/schema.sqlite.sql"
}

func (this *SqliteMemorySqlIpl) IsInMemory(ctx context.Context) bool {
	return true
}

func (this *SqliteMemorySqlIpl) SetMaxOpenConns(db *sql.DB, n int) {
	db.SetMaxOpenConns(n)
}

func (this *SqliteMemorySqlIpl) Exec(db *sql.DB, query string, args ...any) (sql.Result, error) {
	return db.Exec(query, args...)
}

func (this *SqliteMemorySqlIpl) BeginTx(ctx context.Context, db *sql.DB, opts *sql.TxOptions) (*sql.Tx, error) {
	return db.BeginTx(ctx, opts)
}

func (this *SqliteMemorySqlIpl) Open(dataSourceName string) (*sql.DB, error) {
	db, err := sql.Open("sqlite3", dataSourceName)
	if err != nil {
		return nil, err
	}
	return db, nil
}

func (this *SqliteMemorySqlIpl) Query(db *sql.DB, query string, args ...any) (*sql.Rows, error) {
	return db.Query(query, args...)
}

func (this *SqliteMemorySqlIpl) QueryRow(db *sql.DB, query string, args ...any) *sql.Row {
	return db.QueryRow(query, args...)
}

func (this *SqliteMemorySqlIpl) Prepare(db *sql.DB, query string) (*sql.Stmt, error) {
	return db.Prepare(query)
}

func (this *SqliteMemorySqlIpl) SaveToDiskFromMemory(ctx context.Context, srcDb *sql.DB, path string) error {
	destDb, err := sql.Open("sqlite3", path)
	if err != nil {
		return err
	}
	defer destDb.Close()

	destDb.SetMaxOpenConns(1)
	println("Dumping memory db into " + path)

	destConn, err := destDb.Conn(ctx)
	if err != nil {
		return err
	}
	defer destConn.Close()

	srcConn, err := srcDb.Conn(ctx)
	if err != nil {
		return err
	}
	defer srcConn.Close()

	err = destConn.Raw(func(destConn interface{}) error {
		return srcConn.Raw(func(srcConn interface{}) error {
			destSQLiteConn, ok := destConn.(*sqlite3.SQLiteConn)
			if !ok {
				return fmt.Errorf("can't convert destination connection to SQLiteConn")
			}

			srcSQLiteConn, ok := srcConn.(*sqlite3.SQLiteConn)
			if !ok {
				return fmt.Errorf("can't convert source connection to SQLiteConn")
			}

			b, err := destSQLiteConn.Backup("main", srcSQLiteConn, "main")
			if err != nil {
				return fmt.Errorf("error initializing SQLite backup: %w", err)
			}

			done, err := b.Step(-1)
			if err != nil {
				return fmt.Errorf("error in stepping backup: %w", err)
			}
			if !done {
				return fmt.Errorf("step of -1, but not done")
			}

			err = b.Finish()
			if err != nil {
				return fmt.Errorf("error finishing backup: %w", err)
			}
			return nil
		})
	})

	if err != nil {
		return err
	}

	return nil
}

func (this *SqliteMemorySqlIpl) LoadIntoMemoryFromDisk(ctx context.Context, destDb *sql.DB, path string) error {
	srcDb, err := sql.Open("sqlite3", path)
	if err != nil {
		return fmt.Errorf("failed to open source database: %w", err)
	}
	defer srcDb.Close()

	srcDb.SetMaxOpenConns(1)
	println("Loading from disk db " + path + " into memory")

	destConn, err := destDb.Conn(ctx)
	if err != nil {
		return fmt.Errorf("failed to get destination connection: %w", err)
	}
	defer destConn.Close()

	srcConn, err := srcDb.Conn(ctx)
	if err != nil {
		return fmt.Errorf("failed to get source connection: %w", err)
	}
	defer srcConn.Close()

	err = destConn.Raw(func(destConn interface{}) error {
		return srcConn.Raw(func(srcConn interface{}) error {
			destSQLiteConn, ok := destConn.(*sqlite3.SQLiteConn)
			if !ok {
				return fmt.Errorf("can't convert destination connection to SQLiteConn")
			}

			srcSQLiteConn, ok := srcConn.(*sqlite3.SQLiteConn)
			if !ok {
				return fmt.Errorf("can't convert source connection to SQLiteConn")
			}

			b, err := destSQLiteConn.Backup("main", srcSQLiteConn, "main")
			if err != nil {
				return fmt.Errorf("error initializing SQLite backup: %w", err)
			}

			done, err := b.Step(-1)
			if err != nil {
				return fmt.Errorf("error in stepping backup: %w", err)
			}
			if !done {
				return fmt.Errorf("step of -1, but not done")
			}

			err = b.Finish()
			if err != nil {
				return fmt.Errorf("error finishing backup: %w", err)
			}
			return nil
		})
	})

	if err != nil {
		return err
	}
	return nil
}

func (this *SqliteMemorySqlIpl) Create(ctx context.Context, dataSourceName string) {
	// Automatic when database is opened
}

func (this *SqliteMemorySqlIpl) Destroy(ctx context.Context, dataSourceName string) {
	// // Untested & unchecked code
	//this.Exec(this.GetDb(ctx), "RESET DATABASE")
}

func (this *SqliteMemorySqlIpl) Placeholder(n int) string {
	return "?"
}
