package data

import (
	"database/sql"
	"fmt"
	"path/filepath"

	gonanoid "github.com/matoous/go-nanoid"
)

type RemoteDatabase struct {
	Connection        *sql.DB
	databaseDirectory string
}

func (db *RemoteDatabase) Initialize() error {
	connection, error := sql.Open("sqlite3", filepath.Join(db.databaseDirectory, "remotes.db"))
	if error != nil {
		return error
	}
	db.Connection = connection

	_, error = db.Connection.Exec(`PRAGMA foreign_keys = ON;`)
	if error != nil {
		return fmt.Errorf("error enabling foreign keys: %s", error)
	}

	_, error = db.Connection.Exec(`
	CREATE TABLE IF NOT EXISTS Remotes (
	    id TEXT PRIMARY KEY,
	    title TEXT NOT NULL
	);`)
	if error != nil {
		return fmt.Errorf("error creating remotes table: %s", error)
	}

	_, error = db.Connection.Exec(`
	CREATE TABLE IF NOT EXISTS Commands (
	    id TEXT PRIMARY KEY,
	    protocol TEXT,
	    commandNumber INTEGER,
	    device INTEGER,
	    commandType TEXT,
	    title TEXT
	);`)
	if error != nil {
		return fmt.Errorf("error creating commands table: %s", error)
	}

	_, error = db.Connection.Exec(`
	CREATE TABLE IF NOT EXISTS RemoteCommands (
	    remote_id TEXT NOT NULL,
	    command_id TEXT NOT NULL,
	    PRIMARY KEY (remote_id, command_id),
	    FOREIGN KEY (remote_id) REFERENCES Remotes(id) ON DELETE CASCADE,
	    FOREIGN KEY (command_id) REFERENCES Commands(id) ON DELETE CASCADE
	);`)
	if error != nil {
		return fmt.Errorf("error creating remote-commands table: %s", error)
	}
	return nil
}

func (db *RemoteDatabase) Close() error {
	return db.Connection.Close()
}

func (db *RemoteDatabase) CreateRemote(remote Remote) (string, error) {
	remoteId, err := gonanoid.Nanoid(8)
	if err != nil {
		return "", err
	}
	queryString := "INSERT INTO Remotes (id, title) VALUES (?, ?)"
	_, err = db.Connection.Exec(queryString, remoteId, remote.Title)
	if err != nil {
		return "", err
	}
	commandIds := []string{}
	for _, command := range remote.Commands {
		commandIds = append(commandIds, command.Id)
	}
	err = db.CreateRemoteCommands(remoteId, commandIds)
	if err != nil {
		return "", err
	}
	return remoteId, nil
}

func (db *RemoteDatabase) CreateRemoteCommands(remoteId string, commandIds []string) error {
	for _, commandId := range commandIds {
		queryString := "INSERT INTO RemoteCommands (remote_id, command_id) VALUES (?, ?)"
		_, err := db.Connection.Exec(queryString, remoteId, commandId)
		if err != nil {
			return err
		}
	}
	return nil
}

func (db *RemoteDatabase) GetRemote(remoteId string) (Remote, error) {
	var remote Remote
	queryString := "SELECT id, title FROM Remotes WHERE id = ?"
	row := db.Connection.QueryRow(queryString, remoteId)
	error := row.Scan(&remote.Id, &remote.Title)
	if error != nil {
		return Remote{}, fmt.Errorf("error scanning remote: %s", error)
	}
	commands, error := db.GetCommandsByRemoteId(remoteId)
	if error != nil {
		return Remote{}, error
	}
	remote.Commands = commands
	return remote, nil
}

func (db *RemoteDatabase) GetRemotes() ([]Remote, error) {
	rows, error := db.Connection.Query("SELECT id, title FROM Remotes")
	if error != nil {
		return nil, fmt.Errorf("error querying remotes: %s", error)
	}
	defer rows.Close()

	remotes := []Remote{}
	for rows.Next() {
		var remote Remote
		error = rows.Scan(&remote.Id, &remote.Title)
		if error != nil {
			return nil, fmt.Errorf("error scanning remote: %s", error)
		}
		remotes = append(remotes, remote)
	}
	return remotes, nil
}

func (db *RemoteDatabase) DeleteRemote(remoteId string) error {
	queryString := "DELETE FROM Remotes WHERE id = ?"
	_, error := db.Connection.Exec(queryString, remoteId)
	if error != nil {
		return fmt.Errorf("error deleting remote %s: %s", remoteId, error)
	}
	return nil
}

func (db *RemoteDatabase) GetCommandsByRemoteId(remoteId string) ([]Command, error) {
	rows, error := db.Connection.Query("SELECT id, protocol, commandNumber, device, commandType, title FROM Commands WHERE id IN (SELECT command_id FROM RemoteCommands WHERE remote_id = ?)", remoteId)
	if error != nil {
		return nil, fmt.Errorf("error querying commands for remote %s: %s", remoteId, error)
	}
	defer rows.Close()

	commands := []Command{}
	for rows.Next() {
		var command Command
		error = rows.Scan(&command.Id, &command.Protocol, &command.CommandNumber, &command.Device, &command.CommandType, &command.Title)
		if error != nil {
			return nil, fmt.Errorf("error scanning command: %s", error)
		}
		commands = append(commands, command)
	}
	return commands, nil
}

func (db *RemoteDatabase) CreateCommand(command Command) (string, error) {
	commandId, err := gonanoid.Nanoid(8)
	if err != nil {
		return "", err
	}
	queryString := "INSERT INTO Commands (id, protocol, commandNumber, device, commandType, title) VALUES (?, ?, ?, ?, ?, ?)"
	_, err = db.Connection.Exec(queryString, commandId, command.Protocol, command.CommandNumber, command.Device, command.CommandType, command.Title)
	if err != nil {
		return "", err
	}
	return commandId, nil
}

func (db *RemoteDatabase) CreateCommands(commands []Command) error {
	for _, command := range commands {
		_, err := db.CreateCommand(command)
		if err != nil {
			return err
		}
	}
	return nil
}

func (db *RemoteDatabase) GetCommands() ([]Command, error) {
	rows, error := db.Connection.Query("SELECT id, protocol, commandNumber, device, commandType, title FROM Commands")
	if error != nil {
		return nil, fmt.Errorf("error querying commands: %s", error)
	}
	defer rows.Close()

	commands := []Command{}
	for rows.Next() {
		var command Command
		error = rows.Scan(&command.Id, &command.Protocol, &command.CommandNumber, &command.Device, &command.CommandType, &command.Title)
		if error != nil {
			return nil, fmt.Errorf("error scanning command: %s", error)
		}
		commands = append(commands, command)
	}
	return commands, nil
}

func (db *RemoteDatabase) DeleteCommand(commandId string) error {
	queryString := "DELETE FROM Commands WHERE id = ?"
	_, error := db.Connection.Exec(queryString, commandId)
	if error != nil {
		return fmt.Errorf("error deleting command %s: %s", commandId, error)
	}
	return nil
}

func (db *RemoteDatabase) SetDirectory(directory string) {
	db.databaseDirectory = directory
}
