package postgres

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

	"github.com/google/uuid"
	"go.uber.org/zap"

	"github.com/RyanCopley/skybridge/faas/internal/domain"
	"github.com/RyanCopley/skybridge/faas/internal/repository"
)

type functionRepository struct {
	db     *sql.DB
	logger *zap.Logger
}

func NewFunctionRepository(db *sql.DB, logger *zap.Logger) repository.FunctionRepository {
	return &functionRepository{
		db:     db,
		logger: logger,
	}
}

func (r *functionRepository) Create(ctx context.Context, function *domain.FunctionDefinition) (*domain.FunctionDefinition, error) {
	envJSON, err := json.Marshal(function.Environment)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal environment: %w", err)
	}

	query := `
		INSERT INTO functions (id, name, app_id, runtime, image, handler, code, environment, timeout, memory, 
			owner_type, owner_name, owner_owner, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
		RETURNING created_at, updated_at`

	timeoutValue, _ := function.Timeout.Value()
	err = r.db.QueryRowContext(ctx, query,
		function.ID, function.Name, function.AppID, function.Runtime, function.Image,
		function.Handler, function.Code, envJSON, timeoutValue,
		function.Memory, function.Owner.Type, function.Owner.Name, function.Owner.Owner,
		function.CreatedAt, function.UpdatedAt,
	).Scan(&function.CreatedAt, &function.UpdatedAt)

	if err != nil {
		r.logger.Error("Failed to create function", zap.Error(err))
		return nil, fmt.Errorf("failed to create function: %w", err)
	}

	return function, nil
}

func (r *functionRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.FunctionDefinition, error) {
	query := `
		SELECT id, name, app_id, runtime, image, handler, code, environment, timeout, memory,
			owner_type, owner_name, owner_owner, created_at, updated_at
		FROM functions WHERE id = $1`

	function := &domain.FunctionDefinition{}
	var envJSON []byte

	err := r.db.QueryRowContext(ctx, query, id).Scan(
		&function.ID, &function.Name, &function.AppID, &function.Runtime, &function.Image,
		&function.Handler, &function.Code, &envJSON, &function.Timeout, &function.Memory,
		&function.Owner.Type, &function.Owner.Name, &function.Owner.Owner,
		&function.CreatedAt, &function.UpdatedAt,
	)

	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("function not found")
		}
		r.logger.Error("Failed to get function by ID", zap.String("id", id.String()), zap.Error(err))
		return nil, fmt.Errorf("failed to get function: %w", err)
	}

	// Unmarshal environment
	if err := json.Unmarshal(envJSON, &function.Environment); err != nil {
		return nil, fmt.Errorf("failed to unmarshal environment: %w", err)
	}

	return function, nil
}

func (r *functionRepository) GetByName(ctx context.Context, appID, name string) (*domain.FunctionDefinition, error) {
	query := `
		SELECT id, name, app_id, runtime, image, handler, code, environment, timeout, memory,
			owner_type, owner_name, owner_owner, created_at, updated_at
		FROM functions WHERE app_id = $1 AND name = $2`

	function := &domain.FunctionDefinition{}
	var envJSON []byte

	err := r.db.QueryRowContext(ctx, query, appID, name).Scan(
		&function.ID, &function.Name, &function.AppID, &function.Runtime, &function.Image,
		&function.Handler, &function.Code, &envJSON, &function.Timeout, &function.Memory,
		&function.Owner.Type, &function.Owner.Name, &function.Owner.Owner,
		&function.CreatedAt, &function.UpdatedAt,
	)

	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("function not found")
		}
		r.logger.Error("Failed to get function by name", zap.String("app_id", appID), zap.String("name", name), zap.Error(err))
		return nil, fmt.Errorf("failed to get function: %w", err)
	}

	// Unmarshal environment
	if err := json.Unmarshal(envJSON, &function.Environment); err != nil {
		return nil, fmt.Errorf("failed to unmarshal environment: %w", err)
	}

	return function, nil
}

func (r *functionRepository) Update(ctx context.Context, id uuid.UUID, updates *domain.UpdateFunctionRequest) (*domain.FunctionDefinition, error) {
	// First get the current function
	current, err := r.GetByID(ctx, id)
	if err != nil {
		return nil, err
	}

	// Apply updates
	if updates.Name != nil {
		current.Name = *updates.Name
	}
	if updates.Runtime != nil {
		current.Runtime = *updates.Runtime
	}
	if updates.Image != nil {
		current.Image = *updates.Image
	}
	if updates.Handler != nil {
		current.Handler = *updates.Handler
	}
	if updates.Code != nil {
		current.Code = *updates.Code
	}
	if updates.Environment != nil {
		current.Environment = updates.Environment
	}
	if updates.Timeout != nil {
		current.Timeout = *updates.Timeout
	}
	if updates.Memory != nil {
		current.Memory = *updates.Memory
	}
	if updates.Owner != nil {
		current.Owner = *updates.Owner
	}

	// Marshal environment
	envJSON, err := json.Marshal(current.Environment)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal environment: %w", err)
	}

	query := `
		UPDATE functions 
		SET name = $2, runtime = $3, image = $4, handler = $5, code = $6, environment = $7,
			timeout = $8, memory = $9, owner_type = $10, owner_name = $11, owner_owner = $12,
			updated_at = CURRENT_TIMESTAMP
		WHERE id = $1
		RETURNING updated_at`

	timeoutValue, _ := current.Timeout.Value()
	err = r.db.QueryRowContext(ctx, query,
		id, current.Name, current.Runtime, current.Image, current.Handler,
		current.Code, envJSON, timeoutValue, current.Memory,
		current.Owner.Type, current.Owner.Name, current.Owner.Owner,
	).Scan(&current.UpdatedAt)

	if err != nil {
		r.logger.Error("Failed to update function", zap.String("id", id.String()), zap.Error(err))
		return nil, fmt.Errorf("failed to update function: %w", err)
	}

	return current, nil
}

func (r *functionRepository) Delete(ctx context.Context, id uuid.UUID) error {
	query := `DELETE FROM functions WHERE id = $1`

	result, err := r.db.ExecContext(ctx, query, id)
	if err != nil {
		r.logger.Error("Failed to delete function", zap.String("id", id.String()), zap.Error(err))
		return fmt.Errorf("failed to delete function: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get affected rows: %w", err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("function not found")
	}

	return nil
}

func (r *functionRepository) List(ctx context.Context, appID string, limit, offset int) ([]*domain.FunctionDefinition, error) {
	var query string
	var args []interface{}

	if appID != "" {
		query = `
			SELECT id, name, app_id, runtime, image, handler, code, environment, timeout, memory,
				owner_type, owner_name, owner_owner, created_at, updated_at
			FROM functions WHERE app_id = $1
			ORDER BY created_at DESC LIMIT $2 OFFSET $3`
		args = []interface{}{appID, limit, offset}
	} else {
		query = `
			SELECT id, name, app_id, runtime, image, handler, code, environment, timeout, memory,
				owner_type, owner_name, owner_owner, created_at, updated_at
			FROM functions
			ORDER BY created_at DESC LIMIT $1 OFFSET $2`
		args = []interface{}{limit, offset}
	}

	rows, err := r.db.QueryContext(ctx, query, args...)
	if err != nil {
		r.logger.Error("Failed to list functions", zap.Error(err))
		return nil, fmt.Errorf("failed to list functions: %w", err)
	}
	defer rows.Close()

	var functions []*domain.FunctionDefinition
	for rows.Next() {
		function := &domain.FunctionDefinition{}
		var envJSON []byte

		err := rows.Scan(
			&function.ID, &function.Name, &function.AppID, &function.Runtime, &function.Image,
			&function.Handler, &function.Code, &envJSON, &function.Timeout, &function.Memory,
			&function.Owner.Type, &function.Owner.Name, &function.Owner.Owner,
			&function.CreatedAt, &function.UpdatedAt,
		)

		if err != nil {
			r.logger.Error("Failed to scan function", zap.Error(err))
			return nil, fmt.Errorf("failed to scan function: %w", err)
		}

		// Unmarshal environment
		if err := json.Unmarshal(envJSON, &function.Environment); err != nil {
			return nil, fmt.Errorf("failed to unmarshal environment: %w", err)
		}

		functions = append(functions, function)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("failed to iterate functions: %w", err)
	}

	return functions, nil
}

func (r *functionRepository) GetByAppID(ctx context.Context, appID string) ([]*domain.FunctionDefinition, error) {
	return r.List(ctx, appID, 1000, 0) // Get all functions for the app
}
