package postgres

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

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

type InstanceRepository struct {
	db *DB
}

func NewInstanceRepository(db *DB) repository.InstanceRepository {
	return &InstanceRepository{db: db}
}

func (r *InstanceRepository) Create(ctx context.Context, instance *entity.Instance) error {
	if instance.ID == "" {
		instance.ID = uuid.New().String()
	}
	valuesJSON, err := json.Marshal(instance.Values)
	if err != nil {
		return fmt.Errorf("failed to marshal values: %w", err)
	}
	query := `
		INSERT INTO instances
			(id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version,
			 description, values, values_yaml, status, status_reason, last_operation, last_error, revision, created_at, updated_at)
		VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20)
	`
	_, err = r.db.conn.ExecContext(ctx, query,
		instance.ID,
		instance.WorkspaceID,
		instance.OwnerID,
		instance.ClusterID,
		instance.Name,
		instance.Namespace,
		instance.RegistryID,
		instance.Repository,
		instance.Chart,
		instance.Version,
		instance.Description,
		valuesJSON,
		instance.ValuesYAML,
		instance.Status,
		instance.StatusReason,
		instance.LastOperation,
		instance.LastError,
		instance.Revision,
		instance.CreatedAt,
		instance.UpdatedAt,
	)
	if err != nil {
		return fmt.Errorf("failed to create instance: %w", err)
	}
	return nil
}

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

func (r *InstanceRepository) GetByClusterAndName(ctx context.Context, clusterID, name string) (*entity.Instance, error) {
	query := `
		SELECT id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version,
		       description, values, values_yaml, status, status_reason, last_operation, last_error,
		       revision, created_at, updated_at
		FROM instances
		WHERE cluster_id = $1 AND name = $2
	`
	rows, err := r.db.conn.QueryContext(ctx, query, clusterID, name)
	if err != nil {
		return nil, fmt.Errorf("failed to get instance: %w", err)
	}
	defer rows.Close()
	if !rows.Next() {
		return nil, entity.ErrInstanceNotFound
	}
	return r.scanInstance(rows)
}

func (r *InstanceRepository) get(ctx context.Context, where string, arg interface{}) (*entity.Instance, error) {
	query := fmt.Sprintf(`
		SELECT id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version,
		       description, values, values_yaml, status, status_reason, last_operation, last_error,
		       revision, created_at, updated_at
		FROM instances
		WHERE %s
	`, where)
	rows, err := r.db.conn.QueryContext(ctx, query, arg)
	if err != nil {
		return nil, fmt.Errorf("failed to get instance: %w", err)
	}
	defer rows.Close()
	if !rows.Next() {
		return nil, entity.ErrInstanceNotFound
	}
	return r.scanInstance(rows)
}

func (r *InstanceRepository) Update(ctx context.Context, instance *entity.Instance) error {
	instance.UpdatedAt = time.Now()
	valuesJSON, err := json.Marshal(instance.Values)
	if err != nil {
		return fmt.Errorf("failed to marshal values: %w", err)
	}
	query := `
		UPDATE instances
		SET workspace_id = $1, owner_id = $2, cluster_id = $3, name = $4, namespace = $5,
		    registry_id = $6, repository = $7, chart = $8, version = $9, description = $10,
		    values = $11, values_yaml = $12, status = $13, status_reason = $14,
		    last_operation = $15, last_error = $16, revision = $17, updated_at = $18
		WHERE id = $19
	`
	result, err := r.db.conn.ExecContext(ctx, query,
		instance.WorkspaceID,
		instance.OwnerID,
		instance.ClusterID,
		instance.Name,
		instance.Namespace,
		instance.RegistryID,
		instance.Repository,
		instance.Chart,
		instance.Version,
		instance.Description,
		valuesJSON,
		instance.ValuesYAML,
		instance.Status,
		instance.StatusReason,
		instance.LastOperation,
		instance.LastError,
		instance.Revision,
		instance.UpdatedAt,
		instance.ID,
	)
	if err != nil {
		return fmt.Errorf("failed to update instance: %w", err)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get affected rows: %w", err)
	}
	if rows == 0 {
		return entity.ErrInstanceNotFound
	}
	return nil
}

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

func (r *InstanceRepository) ListByCluster(ctx context.Context, clusterID string) ([]*entity.Instance, error) {
	return r.list(ctx, "WHERE cluster_id = $1", clusterID)
}

func (r *InstanceRepository) List(ctx context.Context) ([]*entity.Instance, error) {
	return r.list(ctx, "", nil)
}

func (r *InstanceRepository) list(ctx context.Context, where string, arg interface{}) ([]*entity.Instance, error) {
	query := `
		SELECT id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version,
		       description, values, values_yaml, status, status_reason, last_operation, last_error,
		       revision, created_at, updated_at
		FROM instances
		` + where + `
		ORDER BY created_at DESC
	`
	var rows *sql.Rows
	var err error
	if where == "" {
		rows, err = r.db.conn.QueryContext(ctx, query)
	} else {
		rows, err = r.db.conn.QueryContext(ctx, query, arg)
	}
	if err != nil {
		return nil, fmt.Errorf("failed to list instances: %w", err)
	}
	defer rows.Close()
	instances := make([]*entity.Instance, 0)
	for rows.Next() {
		instance, err := r.scanInstance(rows)
		if err != nil {
			return nil, err
		}
		instances = append(instances, instance)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("rows iteration error: %w", err)
	}
	return instances, nil
}

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

func (r *InstanceRepository) scanInstance(scanner instanceScanner) (*entity.Instance, error) {
	instance := &entity.Instance{}
	var (
		valuesJSON    []byte
		statusReason  sql.NullString
		lastOperation sql.NullString
		lastError     sql.NullString
	)
	err := scanner.Scan(
		&instance.ID,
		&instance.WorkspaceID,
		&instance.OwnerID,
		&instance.ClusterID,
		&instance.Name,
		&instance.Namespace,
		&instance.RegistryID,
		&instance.Repository,
		&instance.Chart,
		&instance.Version,
		&instance.Description,
		&valuesJSON,
		&instance.ValuesYAML,
		&instance.Status,
		&statusReason,
		&lastOperation,
		&lastError,
		&instance.Revision,
		&instance.CreatedAt,
		&instance.UpdatedAt,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to scan instance: %w", err)
	}
	if len(valuesJSON) > 0 {
		if err := json.Unmarshal(valuesJSON, &instance.Values); err != nil {
			return nil, fmt.Errorf("failed to unmarshal values: %w", err)
		}
	}
	if statusReason.Valid {
		instance.StatusReason = statusReason.String
	}
	if lastOperation.Valid {
		instance.LastOperation = entity.InstanceOperation(lastOperation.String)
	}
	if lastError.Valid {
		instance.LastError = lastError.String
	}
	return instance, nil
}
