package store

import (
	"database/sql"
	"errors"
	"fmt"
	"time"

	_ "modernc.org/sqlite"
)

// ErrNotFound is returned when a requested entity does not exist.
var ErrNotFound = errors.New("not found")

// Store wraps the SQLite database connection and provides access to all query methods.
type Store struct {
	db *sql.DB
}

// New opens a SQLite database at the given path and runs auto-migration.
func New(dbPath string) (*Store, error) {
	db, err := sql.Open("sqlite", dbPath)
	if err != nil {
		return nil, fmt.Errorf("open database: %w", err)
	}

	// Enable WAL mode and foreign keys for better concurrency and referential integrity.
	pragmas := []string{
		"PRAGMA journal_mode=WAL",
		"PRAGMA foreign_keys=ON",
		"PRAGMA busy_timeout=5000",
	}
	for _, p := range pragmas {
		if _, err := db.Exec(p); err != nil {
			db.Close()
			return nil, fmt.Errorf("exec pragma %q: %w", p, err)
		}
	}

	s := &Store{db: db}
	if err := s.migrate(); err != nil {
		db.Close()
		return nil, fmt.Errorf("migrate: %w", err)
	}

	return s, nil
}

// Close closes the underlying database connection.
func (s *Store) Close() error {
	return s.db.Close()
}

// DB returns the underlying *sql.DB for advanced operations like transactions.
func (s *Store) DB() *sql.DB {
	return s.db
}

// migrate creates all tables if they do not already exist.
func (s *Store) migrate() error {
	_, err := s.db.Exec(schema)
	return err
}

const schema = `
CREATE TABLE IF NOT EXISTS projects (
	id         TEXT PRIMARY KEY,
	name       TEXT NOT NULL UNIQUE,
	registry   TEXT NOT NULL DEFAULT '',
	image      TEXT NOT NULL,
	port       INTEGER NOT NULL DEFAULT 0,
	healthcheck TEXT NOT NULL DEFAULT '',
	env        TEXT NOT NULL DEFAULT '{}',
	volumes    TEXT NOT NULL DEFAULT '{}',
	created_at TEXT NOT NULL DEFAULT (datetime('now')),
	updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS stages (
	id            TEXT PRIMARY KEY,
	project_id    TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
	name          TEXT NOT NULL,
	tag_pattern   TEXT NOT NULL DEFAULT '*',
	auto_deploy   INTEGER NOT NULL DEFAULT 0,
	max_instances INTEGER NOT NULL DEFAULT 1,
	confirm       INTEGER NOT NULL DEFAULT 0,
	promote_from  TEXT NOT NULL DEFAULT '',
	subdomain     TEXT NOT NULL DEFAULT '',
	created_at    TEXT NOT NULL DEFAULT (datetime('now')),
	updated_at    TEXT NOT NULL DEFAULT (datetime('now')),
	UNIQUE(project_id, name)
);

CREATE TABLE IF NOT EXISTS registries (
	id         TEXT PRIMARY KEY,
	name       TEXT NOT NULL UNIQUE,
	url        TEXT NOT NULL,
	type       TEXT NOT NULL DEFAULT 'generic',
	token      TEXT NOT NULL DEFAULT '',
	created_at TEXT NOT NULL DEFAULT (datetime('now')),
	updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS settings (
	id                  INTEGER PRIMARY KEY CHECK (id = 1),
	domain              TEXT NOT NULL DEFAULT '',
	server_ip           TEXT NOT NULL DEFAULT '',
	network             TEXT NOT NULL DEFAULT '',
	subdomain_pattern   TEXT NOT NULL DEFAULT 'stage-{stage}-{project}',
	notification_url    TEXT NOT NULL DEFAULT '',
	npm_url             TEXT NOT NULL DEFAULT '',
	npm_email           TEXT NOT NULL DEFAULT '',
	npm_password        TEXT NOT NULL DEFAULT '',
	webhook_secret      TEXT NOT NULL DEFAULT '',
	polling_interval    TEXT NOT NULL DEFAULT '5m',
	updated_at          TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS instances (
	id            TEXT PRIMARY KEY,
	stage_id      TEXT NOT NULL REFERENCES stages(id) ON DELETE CASCADE,
	project_id    TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
	container_id  TEXT NOT NULL DEFAULT '',
	image_tag     TEXT NOT NULL,
	subdomain     TEXT NOT NULL DEFAULT '',
	npm_proxy_id  INTEGER NOT NULL DEFAULT 0,
	status        TEXT NOT NULL DEFAULT 'stopped',
	port          INTEGER NOT NULL DEFAULT 0,
	created_at    TEXT NOT NULL DEFAULT (datetime('now')),
	updated_at    TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS deploys (
	id          TEXT PRIMARY KEY,
	project_id  TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
	stage_id    TEXT NOT NULL REFERENCES stages(id) ON DELETE CASCADE,
	instance_id TEXT NOT NULL DEFAULT '',
	image_tag   TEXT NOT NULL,
	status      TEXT NOT NULL DEFAULT 'pending',
	started_at  TEXT NOT NULL DEFAULT (datetime('now')),
	finished_at TEXT NOT NULL DEFAULT '',
	error       TEXT NOT NULL DEFAULT ''
);

CREATE TABLE IF NOT EXISTS deploy_logs (
	id         INTEGER PRIMARY KEY AUTOINCREMENT,
	deploy_id  TEXT NOT NULL REFERENCES deploys(id) ON DELETE CASCADE,
	message    TEXT NOT NULL,
	level      TEXT NOT NULL DEFAULT 'info',
	created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS poll_states (
	stage_id    TEXT PRIMARY KEY REFERENCES stages(id) ON DELETE CASCADE,
	last_tag    TEXT NOT NULL DEFAULT '',
	last_polled TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS users (
	id            TEXT PRIMARY KEY,
	username      TEXT NOT NULL UNIQUE,
	password_hash TEXT NOT NULL DEFAULT '',
	email         TEXT NOT NULL DEFAULT '',
	role          TEXT NOT NULL DEFAULT 'viewer',
	created_at    TEXT NOT NULL DEFAULT (datetime('now')),
	updated_at    TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS auth_settings (
	id                  INTEGER PRIMARY KEY CHECK (id = 1),
	auth_mode           TEXT NOT NULL DEFAULT 'local',
	oidc_client_id      TEXT NOT NULL DEFAULT '',
	oidc_client_secret  TEXT NOT NULL DEFAULT '',
	oidc_issuer_url     TEXT NOT NULL DEFAULT '',
	oidc_redirect_url   TEXT NOT NULL DEFAULT ''
);

-- Seed the settings row if it does not exist.
INSERT OR IGNORE INTO settings (id) VALUES (1);

-- Seed the auth_settings row if it does not exist.
INSERT OR IGNORE INTO auth_settings (id) VALUES (1);

CREATE TABLE IF NOT EXISTS stage_env (
	id         TEXT PRIMARY KEY,
	stage_id   TEXT NOT NULL REFERENCES stages(id) ON DELETE CASCADE,
	key        TEXT NOT NULL,
	value      TEXT NOT NULL DEFAULT '',
	encrypted  INTEGER NOT NULL DEFAULT 0,
	created_at TEXT NOT NULL DEFAULT (datetime('now')),
	updated_at TEXT NOT NULL DEFAULT (datetime('now')),
	UNIQUE(stage_id, key)
);

CREATE TABLE IF NOT EXISTS volumes (
	id         TEXT PRIMARY KEY,
	project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
	source     TEXT NOT NULL,
	target     TEXT NOT NULL,
	mode       TEXT NOT NULL DEFAULT 'shared',
	created_at TEXT NOT NULL DEFAULT (datetime('now')),
	updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
`

// now returns the current time formatted for SQLite storage.
func now() string {
	return time.Now().UTC().Format("2006-01-02 15:04:05")
}
