package services

import (
	"context"
	"fmt"
	"time"

	"go.uber.org/zap"

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

type functionService struct {
	functionRepo   repository.FunctionRepository
	runtimeService RuntimeService
	logger         *zap.Logger
}

func NewFunctionService(functionRepo repository.FunctionRepository, runtimeService RuntimeService, logger *zap.Logger) FunctionService {
	return &functionService{
		functionRepo:   functionRepo,
		runtimeService: runtimeService,
		logger:         logger,
	}
}

func (s *functionService) Create(ctx context.Context, req *domain.CreateFunctionRequest, userID string) (*domain.FunctionDefinition, error) {
	s.logger.Info("Creating new function", 
		zap.String("name", req.Name),
		zap.String("app_id", req.AppID),
		zap.String("user_id", userID))

	// Check if function with same name exists
	_, err := s.functionRepo.GetByName(ctx, req.AppID, req.Name)
	if err == nil {
		return nil, fmt.Errorf("function with name '%s' already exists in app '%s'", req.Name, req.AppID)
	}

	// Validate runtime
	if !s.isValidRuntime(string(req.Runtime)) {
		return nil, fmt.Errorf("unsupported runtime: %s", req.Runtime)
	}

	// Create function definition
	function := &domain.FunctionDefinition{
		ID:          uuid.New(),
		Name:        req.Name,
		AppID:       req.AppID,
		Runtime:     req.Runtime,
		Image:       req.Image,
		Handler:     req.Handler,
		Code:        req.Code,
		Environment: req.Environment,
		Timeout:     req.Timeout,
		Memory:      req.Memory,
		Owner:       req.Owner,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}

	// Validate timeout and memory limits
	if function.Timeout.Duration < time.Second {
		return nil, fmt.Errorf("timeout must be at least 1 second")
	}
	if function.Timeout.Duration > 15*time.Minute {
		return nil, fmt.Errorf("timeout cannot exceed 15 minutes")
	}
	if function.Memory < 64 || function.Memory > 3008 {
		return nil, fmt.Errorf("memory must be between 64 and 3008 MB")
	}

	// Store function
	created, err := s.functionRepo.Create(ctx, function)
	if err != nil {
		s.logger.Error("Failed to create function", 
			zap.String("name", req.Name),
			zap.Error(err))
		return nil, fmt.Errorf("failed to create function: %w", err)
	}

	s.logger.Info("Function created successfully", 
		zap.String("function_id", created.ID.String()),
		zap.String("name", created.Name))

	return created, nil
}

func (s *functionService) GetByID(ctx context.Context, id uuid.UUID) (*domain.FunctionDefinition, error) {
	function, err := s.functionRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("function not found: %w", err)
	}
	return function, nil
}

func (s *functionService) GetByName(ctx context.Context, appID, name string) (*domain.FunctionDefinition, error) {
	function, err := s.functionRepo.GetByName(ctx, appID, name)
	if err != nil {
		return nil, fmt.Errorf("function not found: %w", err)
	}
	return function, nil
}

func (s *functionService) Update(ctx context.Context, id uuid.UUID, req *domain.UpdateFunctionRequest, userID string) (*domain.FunctionDefinition, error) {
	s.logger.Info("Updating function", 
		zap.String("function_id", id.String()),
		zap.String("user_id", userID))

	// Get existing function
	_, err := s.functionRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("function not found: %w", err)
	}

	// Validate runtime if being updated
	if req.Runtime != nil && !s.isValidRuntime(string(*req.Runtime)) {
		return nil, fmt.Errorf("unsupported runtime: %s", *req.Runtime)
	}

	// Validate timeout and memory if being updated
	if req.Timeout != nil {
		if req.Timeout.Duration < time.Second {
			return nil, fmt.Errorf("timeout must be at least 1 second")
		}
		if req.Timeout.Duration > 15*time.Minute {
			return nil, fmt.Errorf("timeout cannot exceed 15 minutes")
		}
	}
	if req.Memory != nil && (*req.Memory < 64 || *req.Memory > 3008) {
		return nil, fmt.Errorf("memory must be between 64 and 3008 MB")
	}

	// Update function
	updated, err := s.functionRepo.Update(ctx, id, req)
	if err != nil {
		s.logger.Error("Failed to update function", 
			zap.String("function_id", id.String()),
			zap.Error(err))
		return nil, fmt.Errorf("failed to update function: %w", err)
	}

	s.logger.Info("Function updated successfully", 
		zap.String("function_id", id.String()))

	return updated, nil
}

func (s *functionService) Delete(ctx context.Context, id uuid.UUID, userID string) error {
	s.logger.Info("Deleting function", 
		zap.String("function_id", id.String()),
		zap.String("user_id", userID))

	// Get function to determine runtime
	function, err := s.functionRepo.GetByID(ctx, id)
	if err != nil {
		return fmt.Errorf("function not found: %w", err)
	}

	// Clean up runtime resources
	backend, err := s.runtimeService.GetBackend(ctx, string(function.Runtime))
	if err != nil {
		s.logger.Warn("Failed to get runtime backend for cleanup", zap.Error(err))
	} else {
		if err := backend.Remove(ctx, id); err != nil {
			s.logger.Warn("Failed to remove runtime resources", 
				zap.String("function_id", id.String()),
				zap.Error(err))
		}
	}

	// Delete function
	if err := s.functionRepo.Delete(ctx, id); err != nil {
		s.logger.Error("Failed to delete function", 
			zap.String("function_id", id.String()),
			zap.Error(err))
		return fmt.Errorf("failed to delete function: %w", err)
	}

	s.logger.Info("Function deleted successfully", 
		zap.String("function_id", id.String()))

	return nil
}

func (s *functionService) List(ctx context.Context, appID string, limit, offset int) ([]*domain.FunctionDefinition, error) {
	if limit <= 0 {
		limit = 50 // Default limit
	}
	if limit > 100 {
		limit = 100 // Max limit
	}

	return s.functionRepo.List(ctx, appID, limit, offset)
}

func (s *functionService) GetByAppID(ctx context.Context, appID string) ([]*domain.FunctionDefinition, error) {
	return s.functionRepo.GetByAppID(ctx, appID)
}

func (s *functionService) Deploy(ctx context.Context, id uuid.UUID, req *domain.DeployFunctionRequest, userID string) (*domain.DeployFunctionResponse, error) {
	s.logger.Info("Deploying function", 
		zap.String("function_id", id.String()),
		zap.String("user_id", userID),
		zap.Bool("force", req.Force))

	// Get function
	function, err := s.functionRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("function not found: %w", err)
	}

	// Get runtime backend
	backend, err := s.runtimeService.GetBackend(ctx, string(function.Runtime))
	if err != nil {
		return nil, fmt.Errorf("failed to get runtime backend: %w", err)
	}

	// Deploy function
	if err := backend.Deploy(ctx, function); err != nil {
		s.logger.Error("Failed to deploy function", 
			zap.String("function_id", id.String()),
			zap.Error(err))
		return nil, fmt.Errorf("failed to deploy function: %w", err)
	}

	s.logger.Info("Function deployed successfully", 
		zap.String("function_id", id.String()),
		zap.String("image", function.Image))

	return &domain.DeployFunctionResponse{
		Status:  "deployed",
		Message: "Function deployed successfully",
		Image:   function.Image,
	}, nil
}

func (s *functionService) isValidRuntime(runtimeType string) bool {
	validRuntimes := []string{
		string(domain.RuntimeNodeJS18),
		string(domain.RuntimePython39),
		string(domain.RuntimeGo120),
		string(domain.RuntimeCustom),
	}

	for _, valid := range validRuntimes {
		if runtimeType == valid {
			return true
		}
	}
	return false
}