package migrations

import (
	"encoding/json"
	"log"
	"os"

	"github.com/jmoiron/sqlx"
	"github.com/knadh/koanf/v2"
	"github.com/knadh/stuffbin"
	"github.com/lib/pq"
)

// V4_0_0 performs the DB migrations.
func V4_0_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf, lo *log.Logger) error {
	lo.Println("IMPORTANT: this upgrade might take a while if you have a large database. Please be patient ...")

	if _, err := db.Exec(`CREATE INDEX IF NOT EXISTS idx_subs_id_status ON subscribers(id, status);`); err != nil {
		return err
	}

	if _, err := db.Exec(`
		CREATE EXTENSION IF NOT EXISTS pgcrypto;

		DO $$
		BEGIN
			IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_type') THEN
			CREATE TYPE user_type AS ENUM ('user', 'api');
			END IF;

			IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_status') THEN
			CREATE TYPE user_status AS ENUM ('enabled', 'disabled');
			END IF;

			IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'role_type') THEN
			CREATE TYPE role_type AS ENUM ('user', 'list');
			END IF;
		END$$;

		CREATE TABLE IF NOT EXISTS roles (
		    id               SERIAL PRIMARY KEY,
		    type             role_type NOT NULL DEFAULT 'user',
		    parent_id        INTEGER NULL REFERENCES roles(id) ON DELETE CASCADE ON UPDATE CASCADE,
		    list_id          INTEGER NULL REFERENCES lists(id) ON DELETE CASCADE ON UPDATE CASCADE,
		    permissions      TEXT[] NOT NULL DEFAULT '{}',
		    name             TEXT NULL,
		    created_at       TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
		    updated_at       TIMESTAMP WITH TIME ZONE DEFAULT NOW()
		);
		CREATE UNIQUE INDEX IF NOT EXISTS idx_roles ON roles (parent_id, list_id);
		CREATE UNIQUE INDEX IF NOT EXISTS idx_roles_name ON roles (type, name) WHERE name IS NOT NULL;

		CREATE TABLE IF NOT EXISTS users (
		    id               SERIAL PRIMARY KEY,
		    username         TEXT NOT NULL UNIQUE,
		    password_login   BOOLEAN NOT NULL DEFAULT false,
		    password         TEXT NULL,
		    email            TEXT NOT NULL UNIQUE,
		    name             TEXT NOT NULL,
		    avatar           TEXT NULL,
		    type             user_type NOT NULL DEFAULT 'user',
		    user_role_id     INTEGER NOT NULL REFERENCES roles(id) ON DELETE RESTRICT,
		    list_role_id     INTEGER NULL REFERENCES roles(id) ON DELETE CASCADE,
		    status           user_status NOT NULL DEFAULT 'disabled',
		    loggedin_at      TIMESTAMP WITH TIME ZONE NULL,
		    created_at       TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
		    updated_at       TIMESTAMP WITH TIME ZONE DEFAULT NOW()
		);

		CREATE TABLE IF NOT EXISTS sessions (
		    id TEXT NOT NULL PRIMARY KEY,
		    data JSONB DEFAULT '{}'::jsonb NOT NULL,
		    created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now() NOT NULL
		);
		CREATE INDEX IF NOT EXISTS idx_sessions ON sessions (id, created_at);
	`); err != nil {
		return err
	}

	// Insert new preference settings.
	if _, err := db.Exec(`
		INSERT INTO settings (key, value) VALUES('security.oidc', '{"enabled": false, "provider_url": "", "client_id": "", "client_secret": ""}') ON CONFLICT DO NOTHING;
	`); err != nil {
		return err
	}

	// Insert superuser role.
	pmRaw, err := fs.Read("/permissions.json")
	if err != nil {
		lo.Fatalf("error reading permissions file: %v", err)
	}
	permGroups := []struct {
		Group       string   `json:"group"`
		Permissions []string `json:"permissions"`
	}{}
	if err := json.Unmarshal(pmRaw, &permGroups); err != nil {
		lo.Fatalf("error loading permissions file: %v", err)
	}

	// Create super admin.
	var (
		user     = os.Getenv("EAGLECAST_ADMIN_USER")
		password = os.Getenv("EAGLECAST_ADMIN_PASSWORD")
		typ      = "env"
	)

	if user != "" {
		// If the env vars are set, use those values
		if len(user) < 2 || len(password) < 8 {
			lo.Fatal("EAGLECAST_ADMIN_USER should be min 3 chars and EAGLECAST_ADMIN_PASSWORD should be min 8 chars")
		}
	} else if ko.Exists("app.admin_username") {
		// Legacy admin/password are set in the config or env var. Use those.
		user = ko.String("app.admin_username")
		password = ko.String("app.admin_password")

		if len(user) < 2 || len(password) < 8 {
			lo.Fatal("admin_username should be min 3 chars and admin_password should be min 8 chars in the TOML config")
		}
		typ = "TOML config"
	}

	if user != "" && password != "" {
		lo.Printf("creating admin user '%s'. Credential source is '%s'", user, typ)

		perms := []string{}
		for _, group := range permGroups {
			perms = append(perms, group.Permissions...)
		}
		if _, err := db.Exec(`INSERT INTO roles (type, name, permissions) VALUES('user', 'Super Admin', $1) ON CONFLICT DO NOTHING`, pq.Array(perms)); err != nil {
			return err
		}

		if _, err := db.Exec(`
		INSERT INTO users (username, password_login, password, email, name, type, user_role_id, status) VALUES($1, true, CRYPT($2, GEN_SALT('bf')), $3, $4, 'user', 1, 'enabled') ON CONFLICT DO NOTHING;
	`, user, password, user+"@eaglecast", user); err != nil {
			return err
		}
	} else {
		lo.Printf("no Super Admin user created. Visit webpage to create user.")
	}

	return nil
}
