package postgres

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

	"github.com/google/uuid"
	"github.com/ocdp/cluster-service/internal/domain/entity"
	"github.com/ocdp/cluster-service/internal/domain/repository"
	"github.com/ocdp/cluster-service/internal/pkg/crypto"
)

type RegistryRepository struct {
	db        *DB
	encryptor crypto.Encryptor
}

func NewRegistryRepository(db *DB, encryptor crypto.Encryptor) repository.RegistryRepository {
	return &RegistryRepository{db: db, encryptor: encryptor}
}

func (r *RegistryRepository) Create(ctx context.Context, registry *entity.Registry) error {
	if registry.ID == "" {
		registry.ID = uuid.New().String()
	}
	encryptedPassword, err := r.encryptor.Encrypt(registry.Password)
	if err != nil {
		return fmt.Errorf("failed to encrypt password: %w", err)
	}
	query := `
		INSERT INTO registries (id, workspace_id, owner_id, visibility, name, url, description, username, password, insecure, created_at, updated_at)
		VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
	`
	_, err = r.db.conn.ExecContext(ctx, query,
		registry.ID,
		registry.WorkspaceID,
		registry.OwnerID,
		registry.Visibility,
		registry.Name,
		registry.URL,
		registry.Description,
		registry.Username,
		encryptedPassword,
		registry.Insecure,
		registry.CreatedAt,
		registry.UpdatedAt,
	)
	if err != nil {
		return fmt.Errorf("failed to create registry: %w", err)
	}
	return nil
}

func (r *RegistryRepository) GetByID(ctx context.Context, id string) (*entity.Registry, error) {
	return r.get(ctx, "id = $1", id)
}

func (r *RegistryRepository) GetByName(ctx context.Context, name string) (*entity.Registry, error) {
	return r.get(ctx, "name = $1", name)
}

func (r *RegistryRepository) get(ctx context.Context, where string, arg interface{}) (*entity.Registry, error) {
	query := fmt.Sprintf(`
		SELECT id, workspace_id, owner_id, visibility, name, url, description, username, password, insecure, created_at, updated_at
		FROM registries
		WHERE %s
	`, where)
	rows, err := r.db.conn.QueryContext(ctx, query, arg)
	if err != nil {
		return nil, fmt.Errorf("failed to get registry: %w", err)
	}
	defer rows.Close()
	if !rows.Next() {
		return nil, entity.ErrRegistryNotFound
	}
	registry, err := r.scanRegistry(rows)
	if err != nil {
		return nil, err
	}
	return registry, nil
}

func (r *RegistryRepository) Update(ctx context.Context, registry *entity.Registry) error {
	registry.UpdatedAt = time.Now()
	encryptedPassword, err := r.encryptor.Encrypt(registry.Password)
	if err != nil {
		return fmt.Errorf("failed to encrypt password: %w", err)
	}
	query := `
		UPDATE registries
		SET workspace_id = $1, owner_id = $2, visibility = $3, name = $4, url = $5,
		    description = $6, username = $7, password = $8, insecure = $9, updated_at = $10
		WHERE id = $11
	`
	result, err := r.db.conn.ExecContext(ctx, query,
		registry.WorkspaceID,
		registry.OwnerID,
		registry.Visibility,
		registry.Name,
		registry.URL,
		registry.Description,
		registry.Username,
		encryptedPassword,
		registry.Insecure,
		registry.UpdatedAt,
		registry.ID,
	)
	if err != nil {
		return fmt.Errorf("failed to update registry: %w", err)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get affected rows: %w", err)
	}
	if rows == 0 {
		return entity.ErrRegistryNotFound
	}
	return nil
}

func (r *RegistryRepository) Delete(ctx context.Context, id string) error {
	result, err := r.db.conn.ExecContext(ctx, `DELETE FROM registries WHERE id = $1`, id)
	if err != nil {
		return fmt.Errorf("failed to delete registry: %w", err)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get affected rows: %w", err)
	}
	if rows == 0 {
		return entity.ErrRegistryNotFound
	}
	return nil
}

func (r *RegistryRepository) List(ctx context.Context) ([]*entity.Registry, error) {
	query := `
		SELECT id, workspace_id, owner_id, visibility, name, url, description, username, password, insecure, created_at, updated_at
		FROM registries
		ORDER BY created_at DESC
	`
	rows, err := r.db.conn.QueryContext(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("failed to list registries: %w", err)
	}
	defer rows.Close()
	registries := make([]*entity.Registry, 0)
	for rows.Next() {
		registry, err := r.scanRegistry(rows)
		if err != nil {
			return nil, err
		}
		registries = append(registries, registry)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("rows iteration error: %w", err)
	}
	return registries, nil
}

type registryScanner interface {
	Scan(dest ...interface{}) error
}

func (r *RegistryRepository) scanRegistry(scanner registryScanner) (*entity.Registry, error) {
	registry := &entity.Registry{}
	var encryptedPassword sql.NullString
	err := scanner.Scan(
		&registry.ID,
		&registry.WorkspaceID,
		&registry.OwnerID,
		&registry.Visibility,
		&registry.Name,
		&registry.URL,
		&registry.Description,
		&registry.Username,
		&encryptedPassword,
		&registry.Insecure,
		&registry.CreatedAt,
		&registry.UpdatedAt,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to scan registry: %w", err)
	}
	registry.Password, err = decryptMaybe(r.encryptor, encryptedPassword.String)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt password: %w", err)
	}
	return registry, nil
}
