package services

import (
	"encoding/json"
	"fmt"
	"time"

	"git.linuxforward.com/byop/byop-engine/dbstore"
	"git.linuxforward.com/byop/byop-engine/models"
	"github.com/google/uuid"
)

// DeploymentService handles business logic for deployments
type DeploymentService struct {
	store         *dbstore.DeploymentStore
	appStore      *dbstore.AppStore
	templateStore *dbstore.TemplateStore
	clientStore   *dbstore.ClientStore
}

// NewDeploymentService creates a new DeploymentService
func NewDeploymentService(
	store *dbstore.DeploymentStore,
	appStore *dbstore.AppStore,
	templateStore *dbstore.TemplateStore,
	clientStore *dbstore.ClientStore,
) *DeploymentService {
	return &DeploymentService{
		store:         store,
		appStore:      appStore,
		templateStore: templateStore,
		clientStore:   clientStore,
	}
}

// CreateDeployment creates a new deployment
func (s *DeploymentService) CreateDeployment(deployment *models.Deployment) error {
	// Generate UUID if not provided
	if deployment.ID == "" {
		deployment.ID = uuid.New().String()
	}

	// Validate the deployment
	if err := s.validateDeployment(deployment); err != nil {
		return fmt.Errorf("invalid deployment: %w", err)
	}

	// Set appropriate status
	deployment.Status = string(models.PENDING_DEPLOYMENT)

	// Set timestamps
	now := time.Now()
	deployment.LastDeployedAt = now

	// Handle deployed apps setup
	if err := s.setupDeployedApps(deployment); err != nil {
		return fmt.Errorf("failed to setup deployed apps: %w", err)
	}

	// Persist the deployment
	if err := s.store.Create(deployment); err != nil {
		return fmt.Errorf("failed to create deployment: %w", err)
	}

	// Trigger deployment process (this would normally be asynchronous)
	// This is a placeholder for your actual deployment logic
	go s.processDeployment(deployment.ID)

	return nil
}

// GetDeployment retrieves a deployment by ID
func (s *DeploymentService) GetDeployment(id string) (*models.Deployment, error) {
	deployment, err := s.store.GetByID(id)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve deployment: %w", err)
	}

	if deployment != nil {
		// Deserialize config fields
		if err := s.deserializeConfigFields(deployment); err != nil {
			return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
		}
	}

	return deployment, nil
}

// UpdateDeployment updates an existing deployment
func (s *DeploymentService) UpdateDeployment(deployment *models.Deployment) error {
	// Validate the deployment ID
	if deployment.ID == "" {
		return fmt.Errorf("deployment ID is required for update")
	}

	// Check if deployment exists
	existingDeployment, err := s.store.GetByID(deployment.ID)
	if err != nil {
		return fmt.Errorf("failed to check if deployment exists: %w", err)
	}
	if existingDeployment == nil {
		return fmt.Errorf("deployment with ID %s not found", deployment.ID)
	}

	// Prevent updates to deployed apps if deployment is not in the right state
	if existingDeployment.Status != string(models.PENDING_DEPLOYMENT) &&
		existingDeployment.Status != string(models.FAILED_DEPLOYMENT) &&
		len(deployment.DeployedApps) > 0 {
		return fmt.Errorf("cannot update deployed apps when deployment is in %s state", existingDeployment.Status)
	}

	// Validate the deployment
	if err := s.validateDeployment(deployment); err != nil {
		return fmt.Errorf("invalid deployment: %w", err)
	}

	// If status was updated to "deploying", update LastDeployedAt
	if existingDeployment.Status != string(models.DEPLOYING) &&
		deployment.Status == string(models.DEPLOYING) {
		deployment.LastDeployedAt = time.Now()
	}

	// Handle deployed apps setup
	if err := s.setupDeployedApps(deployment); err != nil {
		return fmt.Errorf("failed to setup deployed apps: %w", err)
	}

	// Persist the deployment
	if err := s.store.Update(deployment); err != nil {
		return fmt.Errorf("failed to update deployment: %w", err)
	}

	return nil
}

// DeleteDeployment deletes a deployment by ID
func (s *DeploymentService) DeleteDeployment(id string) error {
	// Check if deployment exists
	deployment, err := s.store.GetByID(id)
	if err != nil {
		return fmt.Errorf("failed to check if deployment exists: %w", err)
	}
	if deployment == nil {
		return fmt.Errorf("deployment with ID %s not found", id)
	}

	// Set status to deleting
	deployment.Status = string(models.DELETING)
	if err := s.store.Update(deployment); err != nil {
		return fmt.Errorf("failed to update deployment status: %w", err)
	}

	// Trigger cleanup process (this would normally be asynchronous)
	// This is a placeholder for your actual cleanup logic
	go s.processDeploymentCleanup(id)

	return nil
}

// ListDeployments retrieves all deployments with optional filtering
func (s *DeploymentService) ListDeployments(filter map[string]interface{}) ([]*models.Deployment, error) {
	deployments, err := s.store.List(filter)
	if err != nil {
		return nil, fmt.Errorf("failed to list deployments: %w", err)
	}

	// Deserialize config fields for each deployment
	for _, deployment := range deployments {
		if err := s.deserializeConfigFields(deployment); err != nil {
			return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
		}
	}

	return deployments, nil
}

// GetDeploymentsByClientID retrieves deployments for a specific client
func (s *DeploymentService) GetDeploymentsByClientID(clientID string) ([]*models.Deployment, error) {
	// Check if client exists
	client, err := s.clientStore.GetByID(clientID)
	if err != nil {
		return nil, fmt.Errorf("failed to check if client exists: %w", err)
	}
	if client == nil {
		return nil, fmt.Errorf("client with ID %s not found", clientID)
	}

	deployments, err := s.store.GetByClientID(clientID)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve deployments for client %s: %w", clientID, err)
	}

	// Deserialize config fields for each deployment
	for _, deployment := range deployments {
		if err := s.deserializeConfigFields(deployment); err != nil {
			return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
		}
	}

	return deployments, nil
}

// GetDeploymentsByUserID retrieves deployments created by a specific user
func (s *DeploymentService) GetDeploymentsByUserID(userID string) ([]*models.Deployment, error) {
	deployments, err := s.store.GetByUserID(userID)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve deployments for user %s: %w", userID, err)
	}

	// Deserialize config fields for each deployment
	for _, deployment := range deployments {
		if err := s.deserializeConfigFields(deployment); err != nil {
			return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
		}
	}

	return deployments, nil
}

// GetDeploymentsByTemplateID retrieves deployments based on a specific template
func (s *DeploymentService) GetDeploymentsByTemplateID(templateID string) ([]*models.Deployment, error) {
	// Check if template exists
	template, err := s.templateStore.GetByID(templateID)
	if err != nil {
		return nil, fmt.Errorf("failed to check if template exists: %w", err)
	}
	if template == nil {
		return nil, fmt.Errorf("template with ID %s not found", templateID)
	}

	deployments, err := s.store.GetByTemplateID(templateID)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve deployments for template %s: %w", templateID, err)
	}

	// Deserialize config fields for each deployment
	for _, deployment := range deployments {
		if err := s.deserializeConfigFields(deployment); err != nil {
			return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
		}
	}

	return deployments, nil
}

// UpdateDeploymentStatus updates the status of a deployment
func (s *DeploymentService) UpdateDeploymentStatus(id string, status string) error {
	deployment, err := s.store.GetByID(id)
	if err != nil {
		return fmt.Errorf("failed to retrieve deployment: %w", err)
	}
	if deployment == nil {
		return fmt.Errorf("deployment with ID %s not found", id)
	}

	// Update the status
	deployment.Status = status

	// If status is being set to "deploying", update LastDeployedAt
	if status == string(models.DEPLOYING) {
		deployment.LastDeployedAt = time.Now()
	}

	if err := s.store.Update(deployment); err != nil {
		return fmt.Errorf("failed to update deployment status: %w", err)
	}

	return nil
}

// validateDeployment validates a deployment
func (s *DeploymentService) validateDeployment(deployment *models.Deployment) error {
	// Validate required fields
	if deployment.Name == "" {
		return fmt.Errorf("deployment name is required")
	}

	// Validate relationships
	if deployment.ClientID == "" {
		return fmt.Errorf("client ID is required")
	}
	client, err := s.clientStore.GetByID(deployment.ClientID)
	if err != nil {
		return fmt.Errorf("failed to check client: %w", err)
	}
	if client == nil {
		return fmt.Errorf("client with ID %s not found", deployment.ClientID)
	}

	if deployment.TemplateID == "" {
		return fmt.Errorf("template ID is required")
	}
	template, err := s.templateStore.GetByID(deployment.TemplateID)
	if err != nil {
		return fmt.Errorf("failed to check template: %w", err)
	}
	if template == nil {
		return fmt.Errorf("template with ID %s not found", deployment.TemplateID)
	}

	return nil
}

// setupDeployedApps sets up deployed apps based on the template
func (s *DeploymentService) setupDeployedApps(deployment *models.Deployment) error {
	// If deployment already has deployed apps defined, we assume they're set up correctly
	if len(deployment.DeployedApps) > 0 {
		return nil
	}

	// Get the template
	template, err := s.templateStore.GetByID(deployment.TemplateID)
	if err != nil {
		return fmt.Errorf("failed to retrieve template: %w", err)
	}
	if template == nil {
		return fmt.Errorf("template with ID %s not found", deployment.TemplateID)
	}

	// Use the template config to set up deployed apps
	var templateConfig models.TemplateConfig
	if err := json.Unmarshal([]byte(template.ConfigJSON), &templateConfig); err != nil {
		return fmt.Errorf("failed to parse template config: %w", err)
	}

	// Create deployed apps for each app in the template
	for _, appConfig := range templateConfig.Apps {
		// Get the app
		app, err := s.appStore.GetByID(appConfig.ID)
		if err != nil {
			return fmt.Errorf("failed to retrieve app: %w", err)
		}
		if app == nil {
			return fmt.Errorf("app with ID %s not found", appConfig.ID)
		}

		// Create a deployed app
		deployedApp := models.DeployedApp{
			ID:           uuid.New().String(),
			DeploymentID: deployment.ID,
			AppID:        app.ID,
			Status:       string(models.PENDING_APP),
			Version:      app.Version,
			URL:          "", // Will be set during deployment
			PodCount:     appConfig.Autoscaling.MinReplicas,
			HealthStatus: string(models.HEALTHY),
			Resources: models.ResourceAllocation{
				CPU:     appConfig.Resources.CPU,
				Memory:  appConfig.Resources.Memory,
				Storage: appConfig.Resources.Storage,
			},
		}

		// Add to deployment
		deployment.DeployedApps = append(deployment.DeployedApps, deployedApp)
	}

	return nil
}

// processDeployment handles the actual deployment process
func (s *DeploymentService) processDeployment(deploymentID string) {
	// This would be an async process in a real system
	// For now, we just update the status after a short delay to simulate the process

	// Update status to deploying
	_ = s.UpdateDeploymentStatus(deploymentID, string(models.DEPLOYING))

	// In a real system, this would be where you'd:
	// 1. Provision infrastructure
	// 2. Deploy containers/apps
	// 3. Configure networking
	// 4. Setup monitoring
	// etc.

	// For this demo, we'll just update the status after a short delay
	time.Sleep(2 * time.Second)

	// Update status to deployed or failed (randomly for demonstration)
	if time.Now().Unix()%2 == 0 { // Random success/failure
		_ = s.UpdateDeploymentStatus(deploymentID, string(models.DEPLOYED))
	} else {
		_ = s.UpdateDeploymentStatus(deploymentID, string(models.FAILED_DEPLOYMENT))
	}
}

// processDeploymentCleanup handles the cleanup process for deleted deployments
func (s *DeploymentService) processDeploymentCleanup(deploymentID string) {
	// This would be an async process in a real system
	// In a real system, this would:
	// 1. Deprovision infrastructure
	// 2. Clean up resources
	// 3. Remove configuration

	// For this demo, we'll just delete after a short delay
	time.Sleep(2 * time.Second)

	// Delete the deployment from the database
	_ = s.store.Delete(deploymentID)
}

// deserializeConfigFields deserializes JSON config fields from strings
func (s *DeploymentService) deserializeConfigFields(deployment *models.Deployment) error {
	// Deserialize logs config
	if deployment.LogsConfig != "" {
		var logsConfig models.LogConfiguration
		if err := json.Unmarshal([]byte(deployment.LogsConfig), &logsConfig); err != nil {
			return fmt.Errorf("failed to unmarshal logs config: %w", err)
		}
		// We could set this on the deployment if needed
	}

	// Deserialize metrics config
	if deployment.MetricsConfig != "" {
		var metricsConfig models.MetricsConfiguration
		if err := json.Unmarshal([]byte(deployment.MetricsConfig), &metricsConfig); err != nil {
			return fmt.Errorf("failed to unmarshal metrics config: %w", err)
		}
		// We could set this on the deployment if needed
	}

	// Deserialize alerts config
	if deployment.AlertsConfig != "" {
		var alertsConfig []models.AlertConfiguration
		if err := json.Unmarshal([]byte(deployment.AlertsConfig), &alertsConfig); err != nil {
			return fmt.Errorf("failed to unmarshal alerts config: %w", err)
		}
		// We could set this on the deployment if needed
	}

	return nil
}
