package main

import (
	"context"
	"database/sql"
	_ "embed"
	"flag"
	"fmt"
	"go-nkode/internal/entities"
	"go-nkode/internal/models"
	"go-nkode/internal/repository"
	sqlite_queue "go-nkode/internal/sqlc"
	"go-nkode/sqlite"
	"log"
	"os"
	"path/filepath"
	"strings"

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

func main() {
	if len(os.Args) < 2 {
		log.Fatal("Please provide a command: build-db")
	}
	switch os.Args[1] {
	case "build-db":
		BuildDB()
	case "create-customer":
		CreateCustomer()
	case "add-user":
		AddUser()
	default:
		log.Fatalf("Unknown command: %s", os.Args[1])
	}
}

func CreateCustomer() {
	cliCmd := flag.NewFlagSet("create-customer", flag.ExitOnError)
	customerIDStr := cliCmd.String("customer-id", "", "Customer UUID")
	dbPath := cliCmd.String("db-path", "", "Path to sqlite database")
	if err := cliCmd.Parse(os.Args[2:]); err != nil {
		log.Fatalf("Failed to parse flags: %v", err)
	}
	customerID, err := models.CustomerIDFromString(*customerIDStr)
	if err != nil {
		log.Fatalf("Failed to parse flags: %v", err)
	}
	ctx := context.Background()
	sqliteDb, err := sqlite_queue.OpenSqliteDb(*dbPath)
	queue, err := sqlite_queue.NewQueue(ctx, sqliteDb)
	queue.Start()
	defer queue.Stop()
	sqliteRepo := repository.NewSqliteRepository(ctx, queue)
	if err != nil {
		log.Fatal("error starting sqlite repo: ", err)
	}
	nkodePolicy := models.NewDefaultNKodePolicy()
	customer, err := entities.NewCustomer(nkodePolicy)
	customer.ID = customerID
	if err != nil {
		log.Fatal(err)
	}
	if err = sqliteRepo.CreateCustomer(*customer); err != nil {
		log.Fatal(err)
	}
}

func AddUser() {
	cliCmd := flag.NewFlagSet("add-user", flag.ExitOnError)
	imgPath := cliCmd.String("img-path", "", "Path to directory with image files to add to database. The total must number must equal attrs-per-key X numb-of-keys")
	imgType := cliCmd.String("img-type", "webp", "Image types webp, svg, png, jpeg, default webp")
	customerIDStr := cliCmd.String("customer-id", "", "Customer ID")
	dbPath := cliCmd.String("db-path", "", "Path to the database")
	userEmailStr := cliCmd.String("user-email", "", "User email")
	attrsPerKey := cliCmd.Int("attrs-per-key", -1, "Attributes per key")
	numbOfKeys := cliCmd.Int("numb-of-keys", -1, "Number of keys")
	nkodeIcons := cliCmd.String("nkode-icons", "", "common separated file names of the users nKode icons with no space. filename order sets the nkode passcode order")
	if err := cliCmd.Parse(os.Args[2:]); err != nil {
		log.Fatalf("Failed to parse flags: %v", err)
	}
	fmt.Println("os args: ", os.Args)
	ctx := context.Background()
	sqliteDb, err := sqlite_queue.OpenSqliteDb(*dbPath)
	queue, err := sqlite_queue.NewQueue(ctx, sqliteDb)
	queue.Start()
	defer queue.Stop()
	sqliteRepo := repository.NewSqliteRepository(ctx, queue)
	if err != nil {
		log.Fatal("error starting sqlite repo: ", err)
	}
	customer, err := validCustomerID(*customerIDStr, &sqliteRepo)
	if err != nil {
		log.Println("db path: ", *dbPath)
		log.Fatal("invalid customer id: ", err)
	}
	validateUserEmail(*userEmailStr, customer.ID, &sqliteRepo)
	kp := entities.KeypadDimension{
		AttrsPerKey: *attrsPerKey,
		NumbOfKeys:  *numbOfKeys,
	}
	if *attrsPerKey < entities.KeypadMin.AttrsPerKey || entities.KeypadMax.AttrsPerKey < *attrsPerKey {
		log.Fatalf("invalid attributes per key valid range is %d-%d", entities.KeypadMin.AttrsPerKey, entities.KeypadMax.AttrsPerKey)
	}
	if *numbOfKeys < entities.KeypadMin.NumbOfKeys || entities.KeypadMax.NumbOfKeys < *numbOfKeys {
		log.Fatalf("invalid number of keys. valid range is %d-%d", entities.KeypadMin.NumbOfKeys, entities.KeypadMax.NumbOfKeys)
	}
	if kp.IsDispersable() {
		log.Fatal("Keypad can't be dispersable")
	}
	imgs := getImgs(*imgPath, *imgType)
	if len(imgs) != kp.TotalAttrs() {
		log.Fatal("svgs in directory not equal to keypad size")
	}
	imgIDs := make([]int, len(imgs))
	for idx, img := range imgs {
		id, err := sqliteRepo.AddSVGIcon(img)
		if err != nil {
			log.Fatal(err)
		}
		imgIDs[idx] = int(id)
	}
	iconNames := strings.Split(*nkodeIcons, ",")
	passcodeIdxs := getPasscodeSvgIdx(iconNames, *imgPath)
	if err = customer.IsValidNKode(kp, passcodeIdxs); err != nil {
		log.Fatal("invalid nkode: ", err)
	}
	userInterface, err := entities.NewUserInterface(&kp, models.SvgIdInterface(imgIDs))
	if err != nil {
		log.Fatal("error creating user interface: ", err)
	}
	user, err := entities.NewUser(customer, *userEmailStr, passcodeIdxs, *userInterface, kp)
	if err != nil {
		log.Fatal("error creating user: ", err)
	}
	if err = sqliteRepo.WriteNewUser(*user); err != nil {
		log.Fatal("error storing user: ", err)
	}
}

func getPasscodeSvgIdx(nkodeSvgFileNames []string, svgDir string) []int {
	files, err := os.ReadDir(svgDir)
	if err != nil {
		log.Fatal(err)
	}
	fileNames := make([]string, 0)
	for _, file := range files {
		if file.IsDir() || filepath.Ext(file.Name()) != ".svg" {
			continue
		}
		fileNames = append(fileNames, file.Name())
	}
	passcode := make([]int, 0)
	for _, fileName := range nkodeSvgFileNames {
		idx := indexOf(fileNames, fileName)
		if idx == -1 {
			log.Fatal("file does not exist in svg dir: ", fileName)
		}
		passcode = append(passcode, idx)
	}
	return passcode
}

func indexOf(slice []string, value string) int {
	for i, v := range slice {
		if v == value {
			return i
		}
	}
	return -1 // not found
}

func getImgs(imgDir, imgType string) []string {
	files, err := os.ReadDir(imgDir)
	if err != nil {
		log.Fatalf("error opening dir: %s with err: %v", imgDir, err)
	}
	imgs := make([]string, 0)
	for _, file := range files {
		if file.IsDir() || filepath.Ext(file.Name()) != "."+imgType {
			continue
		}
		filePath := filepath.Join(imgDir, file.Name())
		content, err := os.ReadFile(filePath)
		if err != nil {
			log.Println("Error reading file:", filePath, err)
			continue
		}
		imgs = append(imgs, string(content))
	}
	return imgs
}

func validCustomerID(id string, repo *repository.SqliteRepository) (entities.Customer, error) {
	cID, err := models.CustomerIDFromString(id)
	if err != nil {
		return entities.Customer{}, err
	}
	customer, err := repo.GetCustomer(cID)
	if err != nil {
		return entities.Customer{}, err
	}
	return *customer, nil
}

func validateUserEmail(email string, customerID models.CustomerID, repo *repository.SqliteRepository) {
	userEmail, err := models.ParseEmail(email)
	if err != nil {
		log.Fatal("user email error: ", err)
	}
	_, err = repo.GetUser(userEmail, customerID)
	if err == nil {
		log.Fatal("user already exists")
	}
	fmt.Println("user email is valid:", userEmail)
}

func BuildDB() {
	sqliteSchema, err := sqlite.FS.ReadFile("schema.sql")
	if err != nil {
		log.Fatal(err)
	}
	cliCmd := flag.NewFlagSet("build-db", flag.ExitOnError)
	dbPath := cliCmd.String("db-path", "", "Path to the database")
	imgPath := cliCmd.String("img-path", "", "Path to the directory with images")
	imgType := cliCmd.String("img-type", "svg", "Image types webp, svg, png, jpeg, default webp")
	if err = cliCmd.Parse(os.Args[2:]); err != nil {
		log.Fatalf("Failed to parse flags: %v", err)
	}
	if err = MakeTables(*dbPath, string(sqliteSchema)); err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()
	sqliteDb, err := sqlite_queue.OpenSqliteDb(*dbPath)
	queue, err := sqlite_queue.NewQueue(ctx, sqliteDb)
	queue.Start()
	defer queue.Stop()
	sqliteRepo := repository.NewSqliteRepository(ctx, queue)
	if err != nil {
		log.Fatal("error starting sqlite repo: ", err)
	}
	numbImgs := IconImgToSqlite(&sqliteRepo, *imgPath, *imgType)
	log.Printf("Successfully added %d Images in %s to the database at %s\n", numbImgs, *imgPath, *dbPath)
}

func IconImgToSqlite(repo *repository.SqliteRepository, imgDir, imgType string) int {
	imgs := getImgs(imgDir, imgType)
	for _, img := range imgs {
		if _, err := repo.AddSVGIcon(img); err != nil {
			log.Fatal(err)
		}
	}
	return len(imgs)
}

func MakeTables(dbPath string, schema string) error {
	if _, err := os.Stat(dbPath); os.IsNotExist(err) {
		if err = os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
			return err
		}
		if _, err = os.Create(dbPath); err != nil {
			return err
		}
	}
	db, err := sql.Open("sqlite3", dbPath)
	if err != nil {
		return err
	}
	if _, err = db.Exec(schema); err != nil {
		return err
	}
	return db.Close()
}
