package service

import (
	"context"
	"fmt"
	"strings"

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

// createInitialComponentDeployment creates a K8s Deployment for a newly added component.
// This ensures the deployment exists before CI runs, so kubectl set image succeeds.
// Deployments start with 0 replicas to avoid ImagePullBackOff (image doesn't exist yet).
// The CI deploy step scales to 1 after building and verifying the image.
// For monorepo projects, updates the project's unified Ingress with path-based routing.
// Failures are logged but don't fail the component creation.
func (s *ComponentService) createInitialComponentDeployment(
	ctx context.Context,
	projectID, projectDomain string,
	component *domain.Component,
) {
	// Skip if no deployer or component doesn't need a deployment
	if s.deployer == nil || !component.Type.NeedsPort() {
		return
	}

	// Build the image path - uses "latest" as placeholder until CI builds a real image
	image := fmt.Sprintf("%s/%s/%s:latest", s.registryURL, projectID, component.Name)

	// Assign URL path based on component type
	basePath := assignComponentPath(component)

	// Build sibling service URLs for service discovery
	siblingServices := s.buildSiblingServiceURLs(ctx, projectID, component.Name)

	// Fetch project credentials (DATABASE_URL, REDIS_URL, etc.) from credential store
	secrets := s.fetchProjectCredentials(ctx, projectID)

	spec := domain.DeploySpec{
		ProjectName:     projectID,
		ComponentPath:   component.Path,
		Image:           image,
		Domain:          projectDomain,
		Port:            component.Port,
		Replicas:        -1, // Negative = create with 0 replicas (no pods until CI builds the image)
		BasePath:        basePath,
		SiblingServices: siblingServices,
		Secrets:         secrets,
	}

	log := logging.FromContext(ctx).WithService("component")

	// Create Deployment and Service (without Ingress - we manage that separately)
	if err := s.deployer.Deploy(ctx, spec); err != nil {
		log.Warn("failed to create initial component deployment",
			logging.FieldProjectID, projectID,
			"component", component.Name,
			logging.FieldError, err,
		)
		return
	}

	// Add path to project's unified Ingress
	serviceName := spec.DeploymentName()
	if err := s.deployer.AddIngressPath(ctx, projectID, projectDomain, basePath, serviceName, component.Port); err != nil {
		log.Warn("failed to add ingress path for component",
			logging.FieldProjectID, projectID,
			"component", component.Name,
			"path", basePath,
			logging.FieldError, err,
		)
		// Continue anyway - the deployment/service exist and CI will work
	}

	log.Info("created initial component deployment",
		logging.FieldProjectID, projectID,
		"component", component.Name,
		"image", image,
		"path", basePath,
	)
}

// assignComponentPath determines the URL path for a component.
// Services get /api/{name}, apps get /.
//
// LIMITATION: All app components (app-react, app-astro, app-nextjs) get "/" path.
// If a project has multiple apps, they will conflict on the same path.
// Future enhancement: track assigned paths and use /apps/{name} for additional apps.
func assignComponentPath(component *domain.Component) string {
	switch {
	case component.Type == domain.ComponentTypeService:
		return "/api/" + component.Name
	case component.Type.IsAppComponent():
		return "/"
	default:
		return "" // Workers, CLI don't get HTTP paths
	}
}

// assignPort finds the next available port for a component type.
func (s *ComponentService) assignPort(ctx context.Context, projectID string, componentType domain.ComponentType) (int, error) {
	// Get existing components to find the highest used port
	components, err := s.ListComponents(ctx, projectID)
	if err != nil {
		return 0, err
	}

	startingPort := componentType.StartingPort()
	if startingPort == 0 {
		return 0, nil // Component type doesn't need a port
	}

	maxPort := startingPort - 1
	for _, c := range components {
		// Only consider components that share the same port range
		if c.Type.StartingPort() == startingPort && c.Port > maxPort {
			maxPort = c.Port
		}
	}

	return maxPort + 1, nil
}

// buildSiblingServiceURLs constructs service discovery URLs for all sibling services.
// Returns a map of env var names to internal K8s service URLs.
// Example: {"AUTH_SVC_URL": "http://myproject-auth-svc:8001", "CHAT_SVC_URL": "http://myproject-chat-svc:8002"}
func (s *ComponentService) buildSiblingServiceURLs(ctx context.Context, projectID, currentComponent string) map[string]string {
	components, err := s.ListComponents(ctx, projectID)
	if err != nil {
		log := logging.FromContext(ctx).WithService("component")
		log.Warn("failed to list components for sibling discovery",
			logging.FieldProjectID, projectID,
			logging.FieldError, err,
		)
		return nil
	}

	urls := make(map[string]string)
	for _, c := range components {
		// Skip the current component, non-service types, and components without ports
		if c.Name == currentComponent || c.Type != domain.ComponentTypeService || c.Port == 0 {
			continue
		}

		// Build env var name: auth-svc -> AUTH_SVC_URL
		envKey := toUpperSnake(c.Name) + "_URL"
		// Build internal K8s service URL: http://projectid-componentname:port
		serviceName := projectID + "-" + c.Name
		urls[envKey] = fmt.Sprintf("http://%s:%d", serviceName, c.Port)
	}

	return urls
}

// toUpperSnake converts a kebab-case string to UPPER_SNAKE_CASE.
// Example: "auth-svc" -> "AUTH_SVC"
func toUpperSnake(s string) string {
	return strings.ToUpper(strings.ReplaceAll(s, "-", "_"))
}

// fetchProjectCredentials retrieves stored infrastructure credentials for a project.
// Returns a map of env var names to values (e.g., {"DATABASE_URL": "postgres://...", "REDIS_URL": "redis://..."}).
// Missing credentials are silently skipped - not all projects have all infrastructure.
func (s *ComponentService) fetchProjectCredentials(ctx context.Context, projectID string) map[string]string {
	if s.credentialStore == nil {
		return nil
	}

	// List of infrastructure credentials to fetch
	credentialKeys := []string{
		"DATABASE_URL",
		"DATABASE_URL_STAGING",
		"REDIS_URL",
		"REDIS_URL_STAGING",
		"REDIS_PREFIX",
	}

	// Build scoped keys: "{projectID}:{key}"
	scopedKeys := make([]string, len(credentialKeys))
	for i, key := range credentialKeys {
		scopedKeys[i] = projectID + ":" + key
	}

	// Fetch all credentials in one call
	creds, err := s.credentialStore.GetMultiple(ctx, scopedKeys)
	if err != nil {
		log := logging.FromContext(ctx).WithService("component")
		log.Warn("failed to fetch project credentials",
			logging.FieldProjectID, projectID,
			logging.FieldError, err,
		)
		return nil
	}

	// Convert scoped keys back to env var names
	secrets := make(map[string]string)
	for scopedKey, value := range creds {
		// Extract env var name from scoped key: "myproject:DATABASE_URL" -> "DATABASE_URL"
		parts := strings.SplitN(scopedKey, ":", 2)
		if len(parts) == 2 && value != "" {
			secrets[parts[1]] = value
		}
	}

	if len(secrets) > 0 {
		log := logging.FromContext(ctx).WithService("component")
		log.Debug("fetched project credentials for deployment",
			logging.FieldProjectID, projectID,
			"credential_count", len(secrets),
		)
	}

	return secrets
}
