package postgres

import (
	"context"
	_ "embed" // embed schema
	"log"
	"time"

	"github.com/jackc/pgx/v4/pgxpool"
)

// Include the schema through embedding
//
//go:embed schema.sql
var schema string

// CurrentSchemaVersion is the current version of the schema
const CurrentSchemaVersion = 1

var (
// ErrNotInitialized is returned when the database
// schema is not migrated yet
)

// Status is the database / store status
type Status struct {
	Migrated        bool      `json:"migrated"`
	SchemaVersion   int       `json:"schema_version"`
	SchemaAppliedAt time.Time `json:"schema_applied_at"`
	Error           error     `json:"error"`
}

// Log writes the status into the log
func (s *Status) Log() {
	log.Println(
		"Database migrated:", s.Migrated)
	log.Println(
		"Schema version:", s.SchemaVersion,
		"applied at:", s.SchemaAppliedAt)
}

// The Manager supervises the database. It can migrate the
// schema and retrieve a status.
type Manager struct {
	pool *pgxpool.Pool
}

// NewManager creates a new database manager
func NewManager(pool *pgxpool.Pool) *Manager {
	return &Manager{
		pool: pool,
	}
}

// Start the background jobs for database management
func (m *Manager) Start(ctx context.Context) {
	m.Status(ctx).Log()
}

// Status retrieves the current schema version
// and checks if migrated. In case an error occurs,
// it will be included in the result.
func (m *Manager) Status(ctx context.Context) *Status {
	status := &Status{}
	qry := `
		SELECT version, applied_at FROM __meta__
		 ORDER BY version DESC
		 LIMIT 1
	`
	err := m.pool.QueryRow(ctx, qry).Scan(
		&status.SchemaVersion,
		&status.SchemaAppliedAt,
	)
	if err != nil {
		status.Error = err
		return status
	}

	// Is the database migrated?
	status.Migrated = CurrentSchemaVersion == status.SchemaVersion

	return status
}

// Migrate applies the database initialization script if required.
func (m *Manager) Migrate(ctx context.Context) error {
	s := m.Status(ctx)
	if s.Migrated {
		return nil
	}

	return m.Initialize(ctx)
}

// Initialize will apply the database schema. This will clear the
// database. However for now we treat the state as disposable.
func (m *Manager) Initialize(ctx context.Context) error {
	_, err := m.pool.Exec(ctx, schema)
	if err != nil {
		return err
	}
	return nil
}
