package postgres

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

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

// BlueprintRepository implements port.BlueprintRepository using PostgreSQL.
type BlueprintRepository struct {
	db *sql.DB
}

// NewBlueprintRepository creates a new PostgreSQL blueprint repository.
func NewBlueprintRepository(db *sql.DB) *BlueprintRepository {
	return &BlueprintRepository{db: db}
}

// Ensure BlueprintRepository implements port.BlueprintRepository at compile time.
var _ port.BlueprintRepository = (*BlueprintRepository)(nil)

// CreateBlueprint creates a new blueprint.
func (r *BlueprintRepository) CreateBlueprint(ctx context.Context, blueprint *domain.Blueprint) error {
	specJSON, err := json.Marshal(blueprint.Spec)
	if err != nil {
		return fmt.Errorf("marshal spec: %w", err)
	}

	err = r.db.QueryRowContext(ctx, `
		INSERT INTO blueprints (project_id, name, description, spec)
		VALUES ($1, $2, $3, $4)
		RETURNING id, created_at, updated_at
	`, blueprint.ProjectID, blueprint.Name, blueprint.Description, specJSON).Scan(
		&blueprint.ID,
		&blueprint.CreatedAt,
		&blueprint.UpdatedAt,
	)
	if err != nil {
		return fmt.Errorf("create blueprint: %w", err)
	}

	return nil
}

// GetBlueprint retrieves a blueprint by ID.
func (r *BlueprintRepository) GetBlueprint(ctx context.Context, id domain.BlueprintID) (*domain.Blueprint, error) {
	var blueprint domain.Blueprint
	var specJSON []byte
	var description sql.NullString

	err := r.db.QueryRowContext(ctx, `
		SELECT id, project_id, name, description, spec, created_at, updated_at
		FROM blueprints
		WHERE id = $1
	`, id).Scan(
		&blueprint.ID,
		&blueprint.ProjectID,
		&blueprint.Name,
		&description,
		&specJSON,
		&blueprint.CreatedAt,
		&blueprint.UpdatedAt,
	)
	if err == sql.ErrNoRows {
		return nil, domain.ErrBlueprintNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("get blueprint: %w", err)
	}

	if description.Valid {
		blueprint.Description = description.String
	}

	if err := json.Unmarshal(specJSON, &blueprint.Spec); err != nil {
		return nil, fmt.Errorf("unmarshal spec: %w", err)
	}

	return &blueprint, nil
}

// ListBlueprints returns all blueprints for a project.
func (r *BlueprintRepository) ListBlueprints(ctx context.Context, projectID string) ([]*domain.Blueprint, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT id, project_id, name, description, spec, created_at, updated_at
		FROM blueprints
		WHERE project_id = $1
		ORDER BY created_at DESC
	`, projectID)
	if err != nil {
		return nil, fmt.Errorf("list blueprints: %w", err)
	}
	defer rows.Close()

	var blueprints []*domain.Blueprint
	for rows.Next() {
		var blueprint domain.Blueprint
		var specJSON []byte
		var description sql.NullString

		if err := rows.Scan(
			&blueprint.ID,
			&blueprint.ProjectID,
			&blueprint.Name,
			&description,
			&specJSON,
			&blueprint.CreatedAt,
			&blueprint.UpdatedAt,
		); err != nil {
			return nil, fmt.Errorf("scan blueprint: %w", err)
		}

		if description.Valid {
			blueprint.Description = description.String
		}

		if err := json.Unmarshal(specJSON, &blueprint.Spec); err != nil {
			return nil, fmt.Errorf("unmarshal spec: %w", err)
		}

		blueprints = append(blueprints, &blueprint)
	}

	return blueprints, rows.Err()
}

// UpdateBlueprint updates a blueprint's metadata and spec.
func (r *BlueprintRepository) UpdateBlueprint(ctx context.Context, blueprint *domain.Blueprint) error {
	specJSON, err := json.Marshal(blueprint.Spec)
	if err != nil {
		return fmt.Errorf("marshal spec: %w", err)
	}

	result, err := r.db.ExecContext(ctx, `
		UPDATE blueprints
		SET name = $1, description = $2, spec = $3
		WHERE id = $4
	`, blueprint.Name, blueprint.Description, specJSON, blueprint.ID)
	if err != nil {
		return fmt.Errorf("update blueprint: %w", err)
	}

	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("rows affected: %w", err)
	}
	if rows == 0 {
		return domain.ErrBlueprintNotFound
	}

	return nil
}

// DeleteBlueprint deletes a blueprint.
func (r *BlueprintRepository) DeleteBlueprint(ctx context.Context, id domain.BlueprintID) error {
	result, err := r.db.ExecContext(ctx, `
		DELETE FROM blueprints WHERE id = $1
	`, id)
	if err != nil {
		return fmt.Errorf("delete blueprint: %w", err)
	}

	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("rows affected: %w", err)
	}
	if rows == 0 {
		return domain.ErrBlueprintNotFound
	}

	return nil
}
