package services

import (
	"context"
	"fmt"
	"sync"

	"go.uber.org/zap"

	"github.com/RyanCopley/skybridge/faas/internal/domain"
	"github.com/RyanCopley/skybridge/faas/internal/runtime"
	"github.com/RyanCopley/skybridge/faas/internal/runtime/docker"
)

type runtimeService struct {
	backends map[string]runtime.RuntimeBackend
	mutex    sync.RWMutex
	logger   *zap.Logger
	config   *RuntimeConfig
}

type RuntimeConfig struct {
	DefaultRuntime string                            `json:"default_runtime"`
	Backends       map[string]map[string]interface{} `json:"backends"`
}

func NewRuntimeService(logger *zap.Logger, config *RuntimeConfig) RuntimeService {
	if config == nil {
		config = &RuntimeConfig{
			DefaultRuntime: "docker",
			Backends:       make(map[string]map[string]interface{}),
		}
	}

	service := &runtimeService{
		backends: make(map[string]runtime.RuntimeBackend),
		logger:   logger,
		config:   config,
	}

	// Initialize default Docker backend
	if err := service.initializeDockerBackend(); err != nil {
		logger.Warn("Failed to initialize Docker backend", zap.Error(err))
	}

	return service
}

func (s *runtimeService) initializeDockerBackend() error {
	// Use simple Docker backend for now
	dockerBackend, err := docker.NewSimpleDockerRuntime(s.logger)
	if err != nil {
		s.logger.Error("Failed to create Docker runtime", zap.Error(err))
		return err
	}

	s.mutex.Lock()
	s.backends["docker"] = dockerBackend
	s.mutex.Unlock()

	s.logger.Info("Simple Docker runtime backend initialized")
	return nil
}

func (s *runtimeService) GetBackend(ctx context.Context, runtimeType string) (runtime.RuntimeBackend, error) {
	// Map domain runtime types to backend types
	backendType := s.mapRuntimeToBackend(runtimeType)

	s.mutex.RLock()
	backend, exists := s.backends[backendType]
	s.mutex.RUnlock()

	if !exists {
		return nil, fmt.Errorf("runtime backend '%s' not available", backendType)
	}

	// Check backend health
	if err := backend.HealthCheck(ctx); err != nil {
		s.logger.Warn("Runtime backend health check failed",
			zap.String("backend", backendType),
			zap.Error(err))
		return nil, fmt.Errorf("runtime backend '%s' is not healthy: %w", backendType, err)
	}

	return backend, nil
}

func (s *runtimeService) ListSupportedRuntimes(ctx context.Context) ([]*domain.RuntimeInfo, error) {
	runtimes := []*domain.RuntimeInfo{
		{
			Type:         domain.RuntimeNodeJS18,
			Version:      "18.x",
			Available:    s.isRuntimeAvailable(ctx, "nodejs18"),
			DefaultImage: "node:18-alpine",
			Description:  "Node.js 18.x runtime with Alpine Linux",
		},
		{
			Type:         domain.RuntimePython39,
			Version:      "3.9.x",
			Available:    s.isRuntimeAvailable(ctx, "python3.9"),
			DefaultImage: "python:3.9-alpine",
			Description:  "Python 3.9.x runtime with Alpine Linux",
		},
		{
			Type:         domain.RuntimeGo120,
			Version:      "1.20.x",
			Available:    s.isRuntimeAvailable(ctx, "go1.20"),
			DefaultImage: "golang:1.20-alpine",
			Description:  "Go 1.20.x runtime with Alpine Linux",
		},
		{
			Type:         domain.RuntimeCustom,
			Version:      "custom",
			Available:    s.isRuntimeAvailable(ctx, "custom"),
			DefaultImage: "alpine:latest",
			Description:  "Custom runtime with user-defined image",
		},
	}

	return runtimes, nil
}

func (s *runtimeService) HealthCheck(ctx context.Context, runtimeType string) error {
	backendType := s.mapRuntimeToBackend(runtimeType)

	s.mutex.RLock()
	backend, exists := s.backends[backendType]
	s.mutex.RUnlock()

	if !exists {
		return fmt.Errorf("runtime backend '%s' not available", backendType)
	}

	return backend.HealthCheck(ctx)
}

func (s *runtimeService) GetRuntimeInfo(ctx context.Context, runtimeType string) (*runtime.RuntimeInfo, error) {
	backendType := s.mapRuntimeToBackend(runtimeType)

	s.mutex.RLock()
	backend, exists := s.backends[backendType]
	s.mutex.RUnlock()

	if !exists {
		return nil, fmt.Errorf("runtime backend '%s' not available", backendType)
	}

	return backend.GetInfo(ctx)
}

func (s *runtimeService) ListContainers(ctx context.Context, runtimeType string) ([]runtime.ContainerInfo, error) {
	backendType := s.mapRuntimeToBackend(runtimeType)

	s.mutex.RLock()
	backend, exists := s.backends[backendType]
	s.mutex.RUnlock()

	if !exists {
		return nil, fmt.Errorf("runtime backend '%s' not available", backendType)
	}

	return backend.ListContainers(ctx)
}

func (s *runtimeService) mapRuntimeToBackend(runtimeType string) string {
	// For now, all runtimes use Docker backend
	// In the future, we could support different backends for different runtimes
	switch runtimeType {
	case string(domain.RuntimeNodeJS18):
		return "docker"
	case string(domain.RuntimePython39):
		return "docker"
	case string(domain.RuntimeGo120):
		return "docker"
	case string(domain.RuntimeCustom):
		return "docker"
	default:
		return s.config.DefaultRuntime
	}
}

func (s *runtimeService) isRuntimeAvailable(ctx context.Context, runtimeType string) bool {
	backendType := s.mapRuntimeToBackend(runtimeType)

	s.mutex.RLock()
	backend, exists := s.backends[backendType]
	s.mutex.RUnlock()

	if !exists {
		return false
	}

	if err := backend.HealthCheck(ctx); err != nil {
		return false
	}

	return true
}
