package main

import (
	"database/sql"
	"encoding/json"
	"log"
	"os"
	"path/filepath"
	"strings"

	_ "modernc.org/sqlite"
)

// DB is the global SQLite connection shared by all handlers.
var DB *sql.DB

const schema = `
CREATE TABLE IF NOT EXISTS ssh_profiles (
    id          INTEGER  PRIMARY KEY AUTOINCREMENT,
    name        TEXT     NOT NULL UNIQUE,
    alias       TEXT     NOT NULL,
    host        TEXT     NOT NULL,
    port        INTEGER  NOT NULL DEFAULT 22,
    username    TEXT     NOT NULL,
    password    TEXT     NOT NULL DEFAULT '',
    private_key TEXT     NOT NULL DEFAULT '',
    passphrase  TEXT     NOT NULL DEFAULT '',
    created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS commands (
    id         INTEGER  PRIMARY KEY AUTOINCREMENT,
    alias      TEXT     NOT NULL,
    command    TEXT     NOT NULL,
    sort_order INTEGER  NOT NULL DEFAULT 0,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS scripts (
    id         INTEGER  PRIMARY KEY AUTOINCREMENT,
    name       TEXT     NOT NULL UNIQUE,
    content    TEXT     NOT NULL DEFAULT '',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
`

// initDB opens the SQLite database, applies PRAGMAs and creates tables.
func initDB() error {
	if err := os.MkdirAll(dataBasePath(), 0o750); err != nil {
		return err
	}
	db, err := sql.Open("sqlite", dbPath())
	if err != nil {
		return err
	}
	// Single writer; multiple readers are fine via WAL.
	db.SetMaxOpenConns(1)
	if _, err := db.Exec(`PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;`); err != nil {
		return err
	}
	if _, err := db.Exec(schema); err != nil {
		return err
	}
	DB = db
	log.Printf("SQLite database ready: %s", dbPath())
	return nil
}

// ─── One-time migration from legacy file storage ─────────────────────────────

// migrateFromFiles reads legacy JSON/text files and inserts their data into
// SQLite (INSERT OR IGNORE so duplicates are skipped). After migration each
// source file is renamed to *.migrated so the routine is not repeated.
func migrateFromFiles() {
	migrateSSHProfiles()
	migrateCommands()
	migrateScripts()
}

func migrateSSHProfiles() {
	dir := filepath.Join(dataBasePath(), "ssh")
	entries, err := os.ReadDir(dir)
	if err != nil {
		return // directory doesn't exist — nothing to migrate
	}
	migrated := 0
	for _, e := range entries {
		if e.IsDir() || !strings.HasSuffix(e.Name(), ".json") {
			continue
		}
		path := filepath.Join(dir, e.Name())
		raw, err := os.ReadFile(path)
		if err != nil {
			log.Printf("migrate ssh: read %s: %v", path, err)
			continue
		}
		var p SSHProfile
		if err := json.Unmarshal(raw, &p); err != nil {
			log.Printf("migrate ssh: parse %s: %v", path, err)
			continue
		}
		name := strings.TrimSuffix(e.Name(), ".json")
		if p.Port == 0 {
			p.Port = 22
		}
		_, err = DB.Exec(
			`INSERT OR IGNORE INTO ssh_profiles (name,alias,host,port,username,password,private_key,passphrase)
			 VALUES (?,?,?,?,?,?,?,?)`,
			name, p.Alias, p.Host, p.Port, p.Username, p.Password, p.PrivateKey, p.Passphrase,
		)
		if err != nil {
			log.Printf("migrate ssh: insert %s: %v", name, err)
			continue
		}
		_ = os.Rename(path, path+".migrated")
		migrated++
	}
	if migrated > 0 {
		log.Printf("migrate: imported %d SSH profile(s) from %s", migrated, dir)
	}
}

func migrateCommands() {
	path := filepath.Join(dataBasePath(), "command", "command.json")
	raw, err := os.ReadFile(path)
	if err != nil {
		return
	}
	var cmds []Command
	if err := json.Unmarshal(raw, &cmds); err != nil {
		log.Printf("migrate commands: parse %s: %v", path, err)
		return
	}
	// Check if commands table is already populated to avoid duplicating on restart.
	var count int
	_ = DB.QueryRow(`SELECT COUNT(*) FROM commands`).Scan(&count)
	if count > 0 {
		log.Printf("migrate commands: table not empty, skipping file migration")
		_ = os.Rename(path, path+".migrated")
		return
	}
	for i, cmd := range cmds {
		if _, err := DB.Exec(
			`INSERT INTO commands (alias, command, sort_order) VALUES (?,?,?)`,
			cmd.Alias, cmd.Command, i,
		); err != nil {
			log.Printf("migrate commands: insert [%d]: %v", i, err)
		}
	}
	_ = os.Rename(path, path+".migrated")
	log.Printf("migrate: imported %d command(s) from %s", len(cmds), path)
}

func migrateScripts() {
	dir := filepath.Join(dataBasePath(), "script")
	entries, err := os.ReadDir(dir)
	if err != nil {
		return
	}
	migrated := 0
	for _, e := range entries {
		if e.IsDir() {
			continue
		}
		path := filepath.Join(dir, e.Name())
		content, err := os.ReadFile(path)
		if err != nil {
			log.Printf("migrate scripts: read %s: %v", path, err)
			continue
		}
		if _, err := DB.Exec(
			`INSERT OR IGNORE INTO scripts (name, content) VALUES (?,?)`,
			e.Name(), string(content),
		); err != nil {
			log.Printf("migrate scripts: insert %s: %v", e.Name(), err)
			continue
		}
		_ = os.Rename(path, path+".migrated")
		migrated++
	}
	if migrated > 0 {
		log.Printf("migrate: imported %d script(s) from %s", migrated, dir)
	}
}
