package nkode

import (
	"database/sql"
	"errors"
	"fmt"
	"github.com/google/uuid"
	_ "github.com/mattn/go-sqlite3" // Import the SQLite3 driver
	m "go-nkode/core/model"
	"go-nkode/util"
	"log"
)

type SqliteDB struct {
	path string
}

func NewSqliteDB(path string) (*SqliteDB, error) {
	sqldb := SqliteDB{path: path}
	err := sqldb.NewTables()

	return &sqldb, err
}

func (d *SqliteDB) NewTables() error {
	db, err := sql.Open("sqlite3", d.path)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	createTables := `
PRAGMA foreign_keys = ON;

CREATE TABLE IF NOT EXISTS customer (
    id TEXT NOT NULL PRIMARY KEY,
    max_nkode_len INTEGER NOT NULL,
    min_nkode_len INTEGER NOT NULL,
    distinct_sets INTEGER NOT NULL,
    distinct_attributes INTEGER NOT NULL,
    lock_out INTEGER NOT NULL,
    expiration INTEGER NOT NULL,
    attribute_values BLOB NOT NULL,
    set_values BLOB NOT NULL
);

CREATE TABLE IF NOT EXISTS user (
	id TEXT NOT NULL PRIMARY KEY,
	username TEXT NOT NULL,
	renew INT NOT NULL,
	customer_id TEXT NOT NULL,
	
-- Enciphered Passcode	
	code TEXT NOT NULL,
	mask TEXT NOT NULL,

-- Keypad Dimensions
	attributes_per_key INT NOT NULL,
	number_of_keys INT NOT NULL,

-- User Keys
	alpha_key BLOB NOT NULL,
	set_key BLOB NOT NULL,
	pass_key BLOB NOT NULL,
	mask_key BLOB NOT NULL,
	salt BLOB NOT NULL,
	max_nkode_len INT NOT NULL,

-- User Interface 
	idx_interface BLOB NOT NULL,
	
	
	FOREIGN KEY (customer_id) REFERENCES customers(id),
	UNIQUE(customer_id, username)
);
`

	_, err = db.Exec(createTables)
	if err != nil {
		return err
	}

	return nil
}

func (d *SqliteDB) WriteNewCustomer(c m.Customer) error {
	db, err := sql.Open("sqlite3", d.path)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
	insertCustomer := `
INSERT INTO customer (id, max_nkode_len, min_nkode_len, distinct_sets, distinct_attributes, lock_out, expiration, attribute_values, set_values)
VALUES (?,?,?,?,?,?,?,?,?)
`
	_, err = db.Exec(insertCustomer, uuid.UUID(c.Id), c.NKodePolicy.MaxNkodeLen, c.NKodePolicy.MinNkodeLen, c.NKodePolicy.DistinctSets, c.NKodePolicy.DistinctAttributes, c.NKodePolicy.LockOut, c.NKodePolicy.Expiration, c.Attributes.AttrBytes(), c.Attributes.SetBytes())

	return err
}

func (d *SqliteDB) WriteNewUser(u m.User) error {
	db, err := sql.Open("sqlite3", d.path)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
	insertUser := `
INSERT INTO user (id, username, renew, customer_id, code, mask, attributes_per_key, number_of_keys, alpha_key, set_key, pass_key, mask_key, salt, max_nkode_len, idx_interface)
VALUES (?,?, ?,?,?,?,?,?,?,?,?,?,?,?,?)
`
	var renew int
	if u.Renew {
		renew = 1
	} else {
		renew = 0
	}
	_, err = db.Exec(insertUser, uuid.UUID(u.Id), u.Username, renew, uuid.UUID(u.CustomerId), u.EncipheredPasscode.Code, u.EncipheredPasscode.Mask, u.Kp.AttrsPerKey, u.Kp.NumbOfKeys, util.Uint64ArrToByteArr(u.UserKeys.AlphaKey), util.Uint64ArrToByteArr(u.UserKeys.SetKey), util.Uint64ArrToByteArr(u.UserKeys.PassKey), util.Uint64ArrToByteArr(u.UserKeys.MaskKey), u.UserKeys.Salt, u.UserKeys.MaxNKodeLen, util.IntArrToByteArr(u.Interface.IdxInterface))

	return err
}

func (d *SqliteDB) GetCustomer(id m.CustomerId) (*m.Customer, error) {
	db, err := sql.Open("sqlite3", d.path)
	if err != nil {
		return nil, err
	}
	defer db.Close()
	selectCustomer := `SELECT max_nkode_len, min_nkode_len, distinct_sets, distinct_attributes, lock_out, expiration, attribute_values, set_values FROM customer WHERE id = ?`
	rows, err := db.Query(selectCustomer, uuid.UUID(id))

	if !rows.Next() {
		return nil, errors.New(fmt.Sprintf("no new row for customer %s with err %s", id, rows.Err()))
	}

	var maxNKodeLen int
	var minNKodeLen int
	var distinctSets int
	var distinctAttributes int
	var lockOut int
	var expiration int
	var attributeValues []byte
	var setValues []byte
	err = rows.Scan(&maxNKodeLen, &minNKodeLen, &distinctSets, &distinctAttributes, &lockOut, &expiration, &attributeValues, &setValues)
	if err != nil {
		return nil, err
	}

	if rows.Next() {
		return nil, errors.New(fmt.Sprintf("too many rows for customer %s", id))
	}
	customer := m.Customer{
		Id: id,
		NKodePolicy: m.NKodePolicy{
			MaxNkodeLen:        maxNKodeLen,
			MinNkodeLen:        minNKodeLen,
			DistinctSets:       distinctSets,
			DistinctAttributes: distinctAttributes,
			LockOut:            lockOut,
			Expiration:         expiration,
		},
		Attributes: m.NewCustomerAttributesFromBytes(attributeValues, setValues),
	}

	return &customer, nil
}

func (d *SqliteDB) GetUser(username m.Username, customerId m.CustomerId) (*m.User, error) {
	db, err := sql.Open("sqlite3", d.path)
	if err != nil {
		return nil, err
	}
	defer db.Close()

	userSelect := `
SELECT id, renew, code, mask, attributes_per_key, number_of_keys, alpha_key, set_key, pass_key, mask_key, salt, max_nkode_len, idx_interface FROM user 
WHERE user.username = ? AND user.customer_id = ?
`
	rows, err := db.Query(userSelect, string(username), uuid.UUID(customerId).String())
	if !rows.Next() {
		return nil, errors.New(fmt.Sprintf("no new rows for user %s of customer %s", string(username), uuid.UUID(customerId).String()))
	}
	var id string
	var renewVal int
	var code string
	var mask string
	var attrsPerKey int
	var numbOfKeys int
	var alphaKey []byte
	var setKey []byte
	var passKey []byte
	var maskKey []byte
	var salt []byte
	var maxNKodeLen int
	var idxInterface []byte

	err = rows.Scan(&id, &renewVal, &code, &mask, &attrsPerKey, &numbOfKeys, &alphaKey, &setKey, &passKey, &maskKey, &salt, &maxNKodeLen, &idxInterface)
	if rows.Next() {
		return nil, errors.New(fmt.Sprintf("too many rows for user %s of customer %s", username, customerId))
	}

	userId, err := uuid.Parse(id)
	if err != nil {
		return nil, err
	}
	var renew bool
	if renewVal == 0 {
		renew = false
	} else {
		renew = true
	}

	user := m.User{
		Id:         m.UserId(userId),
		CustomerId: customerId,
		Username:   username,
		EncipheredPasscode: m.EncipheredNKode{
			Code: code,
			Mask: mask,
		},
		Kp: m.KeypadDimension{
			AttrsPerKey: attrsPerKey,
			NumbOfKeys:  numbOfKeys,
		},
		UserKeys: m.UserCipherKeys{
			AlphaKey:    util.ByteArrToUint64Arr(alphaKey),
			SetKey:      util.ByteArrToUint64Arr(setKey),
			PassKey:     util.ByteArrToUint64Arr(passKey),
			MaskKey:     util.ByteArrToUint64Arr(maskKey),
			Salt:        salt,
			MaxNKodeLen: maxNKodeLen,
			Kp:          nil,
		},
		Interface: m.UserInterface{
			IdxInterface: util.ByteArrToIntArr(idxInterface),
			Kp:           nil,
		},
		Renew: renew,
	}
	user.Interface.Kp = &user.Kp
	user.UserKeys.Kp = &user.Kp

	return &user, nil
}

func (d *SqliteDB) UpdateUserInterface(id m.UserId, ui m.UserInterface) error {
	db, err := sql.Open("sqlite3", d.path)
	if err != nil {
		return err
	}
	defer db.Close()
	updateUserInterface := `
UPDATE user SET idx_interface = ? WHERE id = ?
`
	_, err = db.Exec(updateUserInterface, util.IntArrToByteArr(ui.IdxInterface), uuid.UUID(id).String())

	return err
}

func (d *SqliteDB) Renew(id m.CustomerId) error {
	customer, err := d.GetCustomer(id)
	if err != nil {
		return err
	}
	setXor, attrXor := customer.RenewKeys()
	renewArgs := []any{util.Uint64ArrToByteArr(customer.Attributes.AttrVals), util.Uint64ArrToByteArr(customer.Attributes.SetVals), uuid.UUID(customer.Id).String()}

	renewExec := `
BEGIN TRANSACTION;

UPDATE customer SET attribute_values = ?, set_values = ? WHERE id = ?;
`

	db, err := sql.Open("sqlite3", d.path)
	if err != nil {
		return err
	}
	defer db.Close()

	userQuery := `
SELECT id, alpha_key, set_key, attributes_per_key, number_of_keys FROM user WHERE customer_id = ?
`
	rows, err := db.Query(userQuery, uuid.UUID(id).String())
	for rows.Next() {
		var userId string
		var alphaBytes []byte
		var setBytes []byte
		var attrsPerKey int
		var numbOfKeys int
		err = rows.Scan(&userId, &alphaBytes, &setBytes, &attrsPerKey, &numbOfKeys)
		if err != nil {
			return err
		}
		user := m.User{
			Id:                 m.UserId{},
			CustomerId:         m.CustomerId{},
			Username:           "",
			EncipheredPasscode: m.EncipheredNKode{},
			Kp: m.KeypadDimension{
				AttrsPerKey: attrsPerKey,
				NumbOfKeys:  numbOfKeys,
			},
			UserKeys: m.UserCipherKeys{
				AlphaKey: util.ByteArrToUint64Arr(alphaBytes),
				SetKey:   util.ByteArrToUint64Arr(setBytes),
			},
			Interface: m.UserInterface{},
			Renew:     false,
		}
		err = user.RenewKeys(setXor, attrXor)
		if err != nil {
			return err
		}
		renewExec += "\nUPDATE user SET alpha_key = ?, set_key = ?, renew = ? WHERE id = ?;"
		renewArgs = append(renewArgs, util.Uint64ArrToByteArr(user.UserKeys.AlphaKey), util.Uint64ArrToByteArr(user.UserKeys.SetKey), 1, userId)
	}
	renewExec += `
COMMIT;
`

	_, err = db.Exec(renewExec, renewArgs...)
	return err
}

func (d *SqliteDB) RefreshUser(user m.User, passcodeIdx []int, customerAttr m.CustomerAttributes) error {
	db, err := sql.Open("sqlite3", d.path)
	if err != nil {
		return err
	}
	defer db.Close()
	err = user.RefreshPasscode(passcodeIdx, customerAttr)
	if err != nil {
		return err
	}
	updateUser := `
UPDATE user SET renew = ?, code = ?, mask = ?, alpha_key = ?, set_key = ?, pass_key = ?, mask_key = ?, salt = ? WHERE id = ?;
`
	_, err = db.Exec(updateUser, 0, user.EncipheredPasscode.Code, user.EncipheredPasscode.Mask, util.Uint64ArrToByteArr(user.UserKeys.AlphaKey), util.Uint64ArrToByteArr(user.UserKeys.SetKey), util.Uint64ArrToByteArr(user.UserKeys.PassKey), util.Uint64ArrToByteArr(user.UserKeys.MaskKey), user.UserKeys.Salt, uuid.UUID(user.Id).String())
	return err
}
