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 ClusterRepository struct {
	db        *DB
	encryptor crypto.Encryptor
}

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

func (r *ClusterRepository) Create(ctx context.Context, cluster *entity.Cluster) error {
	if cluster.ID == "" {
		cluster.ID = uuid.New().String()
	}
	encryptedCAData, encryptedCertData, encryptedKeyData, encryptedToken, err := r.encryptClusterSecrets(cluster)
	if err != nil {
		return err
	}
	query := `
		INSERT INTO clusters
			(id, workspace_id, owner_id, visibility, name, host, ca_data, cert_data, key_data, token, description, default_namespace, created_at, updated_at)
		VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)
	`
	_, err = r.db.conn.ExecContext(ctx, query,
		cluster.ID,
		cluster.WorkspaceID,
		cluster.OwnerID,
		cluster.Visibility,
		cluster.Name,
		cluster.Host,
		encryptedCAData,
		encryptedCertData,
		encryptedKeyData,
		encryptedToken,
		cluster.Description,
		cluster.DefaultNamespace,
		cluster.CreatedAt,
		cluster.UpdatedAt,
	)
	if err != nil {
		return fmt.Errorf("failed to create cluster: %w", err)
	}
	return nil
}

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

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

func (r *ClusterRepository) get(ctx context.Context, where string, arg interface{}) (*entity.Cluster, error) {
	query := fmt.Sprintf(`
		SELECT id, workspace_id, owner_id, visibility, name, host, ca_data, cert_data, key_data, token, description, default_namespace, created_at, updated_at
		FROM clusters
		WHERE %s
	`, where)
	rows, err := r.db.conn.QueryContext(ctx, query, arg)
	if err != nil {
		return nil, fmt.Errorf("failed to get cluster: %w", err)
	}
	defer rows.Close()
	if !rows.Next() {
		return nil, entity.ErrClusterNotFound
	}
	cluster, err := r.scanCluster(rows)
	if err != nil {
		return nil, err
	}
	return cluster, nil
}

func (r *ClusterRepository) Update(ctx context.Context, cluster *entity.Cluster) error {
	cluster.UpdatedAt = time.Now()
	encryptedCAData, encryptedCertData, encryptedKeyData, encryptedToken, err := r.encryptClusterSecrets(cluster)
	if err != nil {
		return err
	}
	query := `
		UPDATE clusters
		SET workspace_id = $1, owner_id = $2, visibility = $3, name = $4, host = $5,
		    ca_data = $6, cert_data = $7, key_data = $8, token = $9, description = $10,
		    default_namespace = $11, updated_at = $12
		WHERE id = $13
	`
	result, err := r.db.conn.ExecContext(ctx, query,
		cluster.WorkspaceID,
		cluster.OwnerID,
		cluster.Visibility,
		cluster.Name,
		cluster.Host,
		encryptedCAData,
		encryptedCertData,
		encryptedKeyData,
		encryptedToken,
		cluster.Description,
		cluster.DefaultNamespace,
		cluster.UpdatedAt,
		cluster.ID,
	)
	if err != nil {
		return fmt.Errorf("failed to update cluster: %w", err)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get affected rows: %w", err)
	}
	if rows == 0 {
		return entity.ErrClusterNotFound
	}
	return nil
}

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

func (r *ClusterRepository) List(ctx context.Context) ([]*entity.Cluster, error) {
	query := `
		SELECT id, workspace_id, owner_id, visibility, name, host, ca_data, cert_data, key_data, token, description, default_namespace, created_at, updated_at
		FROM clusters
		ORDER BY created_at DESC
	`
	rows, err := r.db.conn.QueryContext(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("failed to list clusters: %w", err)
	}
	defer rows.Close()
	clusters := make([]*entity.Cluster, 0)
	for rows.Next() {
		cluster, err := r.scanCluster(rows)
		if err != nil {
			return nil, err
		}
		clusters = append(clusters, cluster)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("rows iteration error: %w", err)
	}
	return clusters, nil
}

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

func (r *ClusterRepository) scanCluster(scanner clusterScanner) (*entity.Cluster, error) {
	cluster := &entity.Cluster{}
	var encryptedCAData, encryptedCertData, encryptedKeyData, encryptedToken sql.NullString
	var defaultNamespace sql.NullString
	err := scanner.Scan(
		&cluster.ID,
		&cluster.WorkspaceID,
		&cluster.OwnerID,
		&cluster.Visibility,
		&cluster.Name,
		&cluster.Host,
		&encryptedCAData,
		&encryptedCertData,
		&encryptedKeyData,
		&encryptedToken,
		&cluster.Description,
		&defaultNamespace,
		&cluster.CreatedAt,
		&cluster.UpdatedAt,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to scan cluster: %w", err)
	}
	cluster.DefaultNamespace = defaultNamespace.String
	var decryptErr error
	cluster.CAData, decryptErr = decryptMaybe(r.encryptor, encryptedCAData.String)
	if decryptErr != nil {
		return nil, fmt.Errorf("failed to decrypt CA data: %w", decryptErr)
	}
	cluster.CertData, decryptErr = decryptMaybe(r.encryptor, encryptedCertData.String)
	if decryptErr != nil {
		return nil, fmt.Errorf("failed to decrypt cert data: %w", decryptErr)
	}
	cluster.KeyData, decryptErr = decryptMaybe(r.encryptor, encryptedKeyData.String)
	if decryptErr != nil {
		return nil, fmt.Errorf("failed to decrypt key data: %w", decryptErr)
	}
	cluster.Token, decryptErr = decryptMaybe(r.encryptor, encryptedToken.String)
	if decryptErr != nil {
		return nil, fmt.Errorf("failed to decrypt token: %w", decryptErr)
	}
	return cluster, nil
}

func (r *ClusterRepository) encryptClusterSecrets(cluster *entity.Cluster) (string, string, string, string, error) {
	ca, err := r.encryptor.Encrypt(cluster.CAData)
	if err != nil {
		return "", "", "", "", fmt.Errorf("failed to encrypt CA data: %w", err)
	}
	cert, err := r.encryptor.Encrypt(cluster.CertData)
	if err != nil {
		return "", "", "", "", fmt.Errorf("failed to encrypt cert data: %w", err)
	}
	key, err := r.encryptor.Encrypt(cluster.KeyData)
	if err != nil {
		return "", "", "", "", fmt.Errorf("failed to encrypt key data: %w", err)
	}
	token, err := r.encryptor.Encrypt(cluster.Token)
	if err != nil {
		return "", "", "", "", fmt.Errorf("failed to encrypt token: %w", err)
	}
	return ca, cert, key, token, nil
}

func decryptMaybe(encryptor crypto.Encryptor, value string) (string, error) {
	if value == "" {
		return "", nil
	}
	return encryptor.Decrypt(value)
}
