// Package cockroach provides CockroachDB database provisioning for projects.
// Creates isolated databases and users for each project.
package cockroach

import (
	"context"
	"crypto/rand"
	"database/sql"
	"encoding/hex"
	"fmt"
	"log/slog"
	"strings"
	"time"

	_ "github.com/lib/pq" // PostgreSQL driver (CockroachDB is PG-compatible)

	"github.com/orchard9/rdev/internal/domain"
)

// Provisioner implements port.DatabaseProvisioner using CockroachDB.
type Provisioner struct {
	db     *sql.DB
	host   string
	port   int
	logger *slog.Logger
}

// Config holds CockroachDB provisioner configuration.
type Config struct {
	Host    string // e.g., "cockroachdb-public.databases.svc.cluster.local"
	Port    int    // e.g., 26257
	User    string // e.g., "root" (for insecure mode)
	SSLMode string // e.g., "disable" (for insecure mode)
}

// NewProvisioner creates a new CockroachDB database provisioner.
func NewProvisioner(cfg Config, logger *slog.Logger) (*Provisioner, error) {
	if cfg.SSLMode == "" {
		cfg.SSLMode = "disable"
	}

	dsn := fmt.Sprintf("postgresql://%s@%s:%d/defaultdb?sslmode=%s",
		cfg.User, cfg.Host, cfg.Port, cfg.SSLMode)

	db, err := sql.Open("postgres", dsn)
	if err != nil {
		return nil, fmt.Errorf("open connection: %w", err)
	}

	// Configure connection pool
	db.SetMaxOpenConns(5)
	db.SetMaxIdleConns(2)
	db.SetConnMaxLifetime(5 * time.Minute)

	// Verify connection
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := db.PingContext(ctx); err != nil {
		return nil, fmt.Errorf("cockroachdb connection failed: %w", err)
	}

	return &Provisioner{
		db:     db,
		host:   cfg.Host,
		port:   cfg.Port,
		logger: logger,
	}, nil
}

// CreateProjectDatabase provisions an isolated database for a project.
func (p *Provisioner) CreateProjectDatabase(ctx context.Context, projectID string) (*domain.DatabaseCredentials, error) {
	dbName := p.databaseNameFor(projectID)
	username := p.usernameFor(projectID)
	password, err := generateToken(32)
	if err != nil {
		return nil, fmt.Errorf("generate password: %w", err)
	}

	// Check if database already exists
	var exists bool
	err = p.db.QueryRowContext(ctx,
		"SELECT EXISTS(SELECT 1 FROM information_schema.schemata WHERE catalog_name = $1)",
		dbName).Scan(&exists)
	if err != nil {
		return nil, fmt.Errorf("check database exists: %w", err)
	}

	if exists {
		p.logger.Warn("database already exists, recreating user",
			"project_id", projectID,
			"database", dbName)
		// Drop existing user to recreate with new password
		_, _ = p.db.ExecContext(ctx, fmt.Sprintf("DROP USER IF EXISTS %s", quoteIdent(username)))
	}

	// Create database
	if _, err := p.db.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", quoteIdent(dbName))); err != nil {
		return nil, fmt.Errorf("create database: %w", err)
	}

	// Create user
	// In CockroachDB insecure mode, passwords are not enforced but we store one for future TLS mode
	if _, err := p.db.ExecContext(ctx, fmt.Sprintf("CREATE USER IF NOT EXISTS %s", quoteIdent(username))); err != nil {
		return nil, fmt.Errorf("create user: %w", err)
	}

	// Grant permissions
	if _, err := p.db.ExecContext(ctx, fmt.Sprintf("GRANT ALL ON DATABASE %s TO %s", quoteIdent(dbName), quoteIdent(username))); err != nil {
		return nil, fmt.Errorf("grant permissions: %w", err)
	}

	// Build connection URL
	// In insecure mode, password is not used in connection, but we store it for future TLS migration
	url := fmt.Sprintf("postgresql://%s@%s:%d/%s?sslmode=disable",
		username, p.host, p.port, dbName)

	p.logger.Info("created project database",
		"project_id", projectID,
		"database", dbName,
		"username", username)

	return &domain.DatabaseCredentials{
		ProjectID:    projectID,
		DatabaseName: dbName,
		Username:     username,
		Password:     password,
		Host:         p.host,
		Port:         p.port,
		SSLMode:      "disable",
		URL:          url,
		URLStaging:   url, // Same for now; separate staging cluster in future
		CreatedAt:    time.Now().UTC(),
	}, nil
}

// DeleteProjectDatabase removes database access for a project.
func (p *Provisioner) DeleteProjectDatabase(ctx context.Context, projectID string) error {
	dbName := p.databaseNameFor(projectID)
	username := p.usernameFor(projectID)

	// Revoke permissions first
	_, _ = p.db.ExecContext(ctx, fmt.Sprintf("REVOKE ALL ON DATABASE %s FROM %s", quoteIdent(dbName), quoteIdent(username)))

	// Drop database (CASCADE drops all tables, indexes, etc.)
	if _, err := p.db.ExecContext(ctx, fmt.Sprintf("DROP DATABASE IF EXISTS %s CASCADE", quoteIdent(dbName))); err != nil {
		p.logger.Warn("failed to drop database", "database", dbName, "error", err)
	}

	// Drop user
	if _, err := p.db.ExecContext(ctx, fmt.Sprintf("DROP USER IF EXISTS %s", quoteIdent(username))); err != nil {
		p.logger.Warn("failed to drop user", "username", username, "error", err)
	}

	p.logger.Info("deleted project database",
		"project_id", projectID,
		"database", dbName,
		"username", username)

	return nil
}

// GetProjectDatabase retrieves database credentials for a project.
// Note: Password cannot be retrieved from CockroachDB; use stored credentials.
func (p *Provisioner) GetProjectDatabase(ctx context.Context, projectID string) (*domain.DatabaseCredentials, error) {
	dbName := p.databaseNameFor(projectID)
	username := p.usernameFor(projectID)

	// Check if database exists
	var exists bool
	err := p.db.QueryRowContext(ctx,
		"SELECT EXISTS(SELECT 1 FROM information_schema.schemata WHERE catalog_name = $1)",
		dbName).Scan(&exists)
	if err != nil {
		return nil, fmt.Errorf("check database exists: %w", err)
	}
	if !exists {
		return nil, nil // Database not provisioned
	}

	// Database exists; construct credentials without password
	url := fmt.Sprintf("postgresql://%s@%s:%d/%s?sslmode=disable",
		username, p.host, p.port, dbName)

	return &domain.DatabaseCredentials{
		ProjectID:    projectID,
		DatabaseName: dbName,
		Username:     username,
		Password:     "", // Not available; use credential store
		Host:         p.host,
		Port:         p.port,
		SSLMode:      "disable",
		URL:          url,
		URLStaging:   url,
	}, nil
}

// TestConnection verifies CockroachDB connectivity.
func (p *Provisioner) TestConnection(ctx context.Context) error {
	return p.db.PingContext(ctx)
}

// Close closes the database connection.
func (p *Provisioner) Close() error {
	return p.db.Close()
}

// databaseNameFor returns the database name for a project.
func (p *Provisioner) databaseNameFor(projectID string) string {
	return "project_" + sanitizeIdentifier(projectID)
}

// usernameFor returns the database username for a project.
func (p *Provisioner) usernameFor(projectID string) string {
	return "project_" + sanitizeIdentifier(projectID)
}

// sanitizeIdentifier sanitizes a string for use as a SQL identifier.
// Replaces non-alphanumeric characters with underscores and lowercases.
func sanitizeIdentifier(s string) string {
	return strings.Map(func(r rune) rune {
		if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' {
			return r
		}
		if r >= 'A' && r <= 'Z' {
			return r + 32 // lowercase
		}
		return '_'
	}, s)
}

// quoteIdent quotes a SQL identifier to prevent injection.
// CockroachDB uses double quotes for identifiers.
func quoteIdent(s string) string {
	return `"` + strings.ReplaceAll(s, `"`, `""`) + `"`
}

// generateToken generates a cryptographically secure random token.
func generateToken(length int) (string, error) {
	bytes := make([]byte, length)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}
