package sq

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"github.com/jmoiron/sqlx"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"os"
	"path/filepath"
	"strings"
)

// HashMattnSqliteSchema
// use if github.com/glebarez/go-sqlite
func HashMattnSqliteSchema(ctx context.Context, schemaStr string) (string, error) {
	dbdir := os.TempDir()
	dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3")

	err := os.MkdirAll(dbdir, os.ModePerm)
	if err != nil {
		return "", err
	}

	url := fmt.Sprintf("file:%s?_journal=%s&_timeout=%d&_fk=%s&_busy_timeout=%d", dbfile1, "DELETE", 1000, "true", 1000)

	xdb, err := sqlx.Open("sqlite3", url)
	if err != nil {
		return "", err
	}

	db := NewDB(xdb, DBOptions{})

	_, err = db.Exec(ctx, schemaStr, PP{})
	if err != nil {
		return "", err
	}

	return HashSqliteDatabase(ctx, db)
}

// HashGoSqliteSchema
// use if mattn/go-sqlite3
func HashGoSqliteSchema(ctx context.Context, schemaStr string) (string, error) {
	dbdir := os.TempDir()
	dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3")

	err := os.MkdirAll(dbdir, os.ModePerm)
	if err != nil {
		return "", err
	}

	url := fmt.Sprintf("file:%s?_pragma=journal_mode(%s)&_pragma=timeout(%d)&_pragma=foreign_keys(%s)&_pragma=busy_timeout(%d)", dbfile1, "DELETE", 1000, "true", 1000)

	xdb, err := sqlx.Open("sqlite", url)
	if err != nil {
		return "", err
	}

	db := NewDB(xdb, DBOptions{})

	_, err = db.Exec(ctx, schemaStr, PP{})
	if err != nil {
		return "", err
	}

	return HashSqliteDatabase(ctx, db)
}

func HashSqliteDatabase(ctx context.Context, db Queryable) (string, error) {
	ss, err := CreateSqliteDatabaseSchemaString(ctx, db)
	if err != nil {
		return "", err
	}

	cs := sha256.Sum256([]byte(ss))

	return hex.EncodeToString(cs[:]), nil
}

func CreateSqliteDatabaseSchemaString(ctx context.Context, db Queryable) (string, error) {

	type colInfo struct {
		Name       string  `db:"name"`
		Type       string  `db:"type"`
		NotNull    string  `db:"notnull"`
		Default    *string `db:"dflt_value"`
		PrimaryKey *string `db:"pk"`
	}

	type idxInfo struct {
		Name   string `json:"name"       db:"name"`
		Unique int    `json:"unique"     db:"unique"`
		Origin string `json:"origin"     db:"origin"`
		Patial int    `json:"partial"    db:"partial"`
	}

	type fkyInfo struct {
		TableDest string `json:"table_dest"  db:"table"`
		From      string `json:"from"        db:"from"`
		To        string `json:"to"          db:"to"`
		OnUpdate  string `json:"on_update"   db:"on_update"`
		OnDelete  string `json:"on_delete"   db:"on_delete"`
		Match     string `json:"match"       db:"match"`
	}

	type tabInfo struct {
		Name   string `json:"name"    db:"name"`
		Type   string `json:"type"    db:"type"`
		NumCol int    `json:"ncol"    db:"ncol"`
		Strict int    `json:"strict"  db:"strict"`

		ColumnInfo []colInfo `json:"-"`
		IndexInfo  []idxInfo `json:"-"`
		FKeyInfo   []fkyInfo `json:"-"`
	}

	rowsTableList, err := db.Query(ctx, "PRAGMA table_list;", PP{})
	if err != nil {
		return "", err
	}
	tableList, err := ScanAll[tabInfo](ctx, db, rowsTableList, SModeFast, Unsafe, true)
	if err != nil {
		return "", err
	}

	langext.SortBy(tableList, func(v tabInfo) string { return v.Name })

	result := make([]tabInfo, 0)

	for i, tab := range tableList {

		if strings.HasPrefix(tab.Name, "sqlite_") {
			continue
		}

		{

			rowsColumnList, err := db.Query(ctx, fmt.Sprintf("PRAGMA table_info(\"%s\");", tab.Name), PP{})
			if err != nil {
				return "", err
			}

			columnList, err := ScanAll[colInfo](ctx, db, rowsColumnList, SModeFast, Unsafe, true)
			if err != nil {
				return "", err
			}

			langext.SortBy(columnList, func(v colInfo) string { return v.Name })

			tableList[i].ColumnInfo = columnList
		}

		{
			rowsIdxList, err := db.Query(ctx, fmt.Sprintf("PRAGMA index_list(\"%s\");", tab.Name), PP{})
			if err != nil {
				return "", err
			}
			idxList, err := ScanAll[idxInfo](ctx, db, rowsIdxList, SModeFast, Unsafe, true)
			if err != nil {
				return "", err
			}

			langext.SortBy(idxList, func(v idxInfo) string { return v.Name })

			tableList[i].IndexInfo = idxList
		}

		{
			rowsIdxList, err := db.Query(ctx, fmt.Sprintf("PRAGMA foreign_key_list(\"%s\");", tab.Name), PP{})
			if err != nil {
				return "", err
			}
			fkyList, err := ScanAll[fkyInfo](ctx, db, rowsIdxList, SModeFast, Unsafe, true)
			if err != nil {
				return "", err
			}

			langext.SortBy(fkyList, func(v fkyInfo) string { return v.From })

			tableList[i].FKeyInfo = fkyList
		}

		result = append(result, tableList[i])
	}

	strBuilderResult := ""
	for _, vTab := range result {
		jbinTable, err := json.Marshal(vTab)
		if err != nil {
			return "", err
		}

		strBuilderResult += fmt.Sprintf("#TABLE: %s\n{\n", string(jbinTable))

		for _, vCol := range vTab.ColumnInfo {
			jbinColumn, err := json.Marshal(vCol)
			if err != nil {
				return "", err
			}

			strBuilderResult += fmt.Sprintf("    COLUMN: %s\n", string(jbinColumn))
		}

		for _, vIdx := range vTab.IndexInfo {
			jbinIndex, err := json.Marshal(vIdx)
			if err != nil {
				return "", err
			}

			strBuilderResult += fmt.Sprintf("    INDEX:  %s\n", string(jbinIndex))
		}

		for _, vFky := range vTab.FKeyInfo {
			jbinFKey, err := json.Marshal(vFky)
			if err != nil {
				return "", err
			}

			strBuilderResult += fmt.Sprintf("    FKEY:   %s\n", string(jbinFKey))
		}

		strBuilderResult += "}\n\n"
	}

	return strBuilderResult, nil
}