package services

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

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

// DeploymentService handles business logic for deployments
type DeploymentService struct {
	store          *dbstore.DeploymentStore
	componentStore *dbstore.ComponentStore
	blueprintStore *dbstore.BlueprintStore
	clientStore    *dbstore.ClientStore
	logger         *logrus.Entry
}

// NewDeploymentService creates a new DeploymentService
func NewDeploymentService(
	store *dbstore.DeploymentStore,
	componentStore *dbstore.ComponentStore,
	blueprintStore *dbstore.BlueprintStore,
	clientStore *dbstore.ClientStore,
) *DeploymentService {
	return &DeploymentService{
		store:          store,
		componentStore: componentStore,
		blueprintStore: blueprintStore,
		clientStore:    clientStore,
		logger:         logrus.WithField("component", "deployment_service"),
	}
}

// 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.CreatedAt = now
	deployment.UpdatedAt = now

	// Get instance from pool
	ctx := context.Background()
	return s.createDeploymentWithNewInstance(ctx, deployment)
}

// createDeploymentWithNewInstance creates a new instance directly from the provider
func (s *DeploymentService) createDeploymentWithNewInstance(ctx context.Context, deployment *models.Deployment) error {
	// Get provider
	provider, ok := cloud.GetProvider(deployment.Provider)
	if !ok {
		return fmt.Errorf("provider %s not found", deployment.Provider)
	}

	blueprint, err := s.blueprintStore.GetByID(deployment.BlueprintID)
	if err != nil {
		return fmt.Errorf("failed to retrieve blueprint: %w", err)
	}

	if blueprint == nil {
		return fmt.Errorf("blueprint with ID %s not found", deployment.BlueprintID)
	}

	var components []models.Component
	for _, component := range blueprint.Config.Components {
		fmt.Printf("Component ID: %s\n", component.ID)
		comp, err := s.componentStore.GetByID(component.ID)
		if err != nil {
			fmt.Errorf("failed to retrieve component: %w", err)
			continue
		}
		if comp == nil {
			fmt.Errorf("component with ID %s not found", component.ID)
			continue
		}

		components = append(components, *comp)
	}

	// Create instance options
	instanceOpts := cloud.InstanceCreateOpts{
		Name:       fmt.Sprintf("deployment-%s", deployment.ID[:8]),
		Components: components,
		Tags: map[string]string{
			"managed-by":    "byop-platform",
			"deployment-id": deployment.ID,
			"client-id":     deployment.ClientID,
			// "component-id":  deployment.ComponentID,
			"blueprint-id": deployment.BlueprintID,
		},
	}

	// Create the instance
	s.logger.WithFields(logrus.Fields{
		"name":     instanceOpts.Name,
		"provider": deployment.Provider,
		"region":   deployment.Region,
		"size":     deployment.InstanceType,
	}).Info("Creating new instance for deployment")

	instance, err := provider.GetFirstFreeInstance(ctx)
	if err != nil {
		return fmt.Errorf("failed to create instance: %w", err)
	}

	// Update deployment with instance info
	deployment.InstanceID = instance.ID
	deployment.IPAddress = instance.IPAddress

	// Save the deployment
	if err := s.store.CreateDeployment(deployment); err != nil {
		// If we fail to save deployment, try to delete the instance
		_ = provider.ResetInstance(ctx, instance.ID)
		return fmt.Errorf("failed to save deployment: %w", err)
	}

	// Start deployment process asynchronously
	go s.processDeployment(ctx, deployment)

	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.DeployedComponents) > 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.setupDeployedComponents(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
}

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

	deployments, err := s.store.GetByBlueprintID(blueprintID)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve deployments for blueprint %s: %w", blueprintID, 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.BlueprintID == "" {
		return fmt.Errorf("blueprint ID is required")
	}
	blueprint, err := s.blueprintStore.GetByID(deployment.BlueprintID)
	if err != nil {
		return fmt.Errorf("failed to check blueprint: %w", err)
	}
	if blueprint == nil {
		return fmt.Errorf("blueprint with ID %s not found", deployment.BlueprintID)
	}

	return nil
}

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

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

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

	// Create deployed apps for each component in the blueprint
	for _, componentConfig := range blueprintConfig.Components {
		// Get the component
		component, err := s.componentStore.GetByID(componentConfig.ID)
		if err != nil {
			return fmt.Errorf("failed to retrieve component: %w", err)
		}
		if component == nil {
			return fmt.Errorf("component with ID %s not found", componentConfig.ID)
		}

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

		// Add to deployment
		deployment.DeployedComponents = append(deployment.DeployedComponents, deployedComponent)
	}

	return nil
}

// processDeployment handles the actual deployment process
func (s *DeploymentService) processDeployment(ctx context.Context, deployment *models.Deployment) {
	// Update status to deploying
	_ = s.UpdateDeploymentStatus(deployment.ID, string(models.DEPLOYING))

	s.logger.WithField("deployment_id", deployment.ID).Info("Starting deployment process")

	// Check if we have a valid instance
	if deployment.InstanceID == "" {
		s.logger.WithField("deployment_id", deployment.ID).Error("No instance ID provided for deployment")
		_ = s.UpdateDeploymentStatus(deployment.ID, string(models.FAILED_DEPLOYMENT))
		return
	}
	// Setup the deployed apps (database setup, migrations, etc.)
	if err := s.setupDeployedComponents(deployment); err != nil {
		s.logger.WithError(err).WithField("deployment_id", deployment.ID).Error("Failed to setup deployed apps")
		_ = s.UpdateDeploymentStatus(deployment.ID, string(models.FAILED_DEPLOYMENT))
		return
	}

	// Update the deployment with completed status
	deployment.Status = string(models.DEPLOYED)
	deployment.LastDeployedAt = time.Now()

	if err := s.store.Update(deployment); err != nil {
		s.logger.WithError(err).WithField("deployment_id", deployment.ID).Error("Failed to update deployment status")
	}

	s.logger.WithField("deployment_id", deployment.ID).Info("Deployment completed successfully")
}

// processDeploymentCleanup handles the cleanup process for deleted deployments
func (s *DeploymentService) processDeploymentCleanup(deploymentID string) {
	// Get the deployment
	deployment, err := s.store.GetByID(deploymentID)
	if err != nil {
		s.logger.WithError(err).WithField("deployment_id", deploymentID).Error("Failed to get deployment for cleanup")
		return
	}

	if deployment == nil {
		s.logger.WithField("deployment_id", deploymentID).Error("Deployment not found for cleanup")
		return
	}

	s.logger.WithField("deployment_id", deploymentID).Info("Starting deployment cleanup process")

	ctx := context.Background()

	// Otherwise, deprovision the instance
	provider, ok := cloud.GetProvider(deployment.Provider)
	if !ok {
		s.logger.WithField("provider", deployment.Provider).Error("Provider not found for cleanup")
	} else {
		s.logger.WithField("instance_id", deployment.InstanceID).Info("Terminating instance")

		if err := provider.ResetInstance(ctx, deployment.InstanceID); err != nil {
			s.logger.WithError(err).WithField("instance_id", deployment.InstanceID).
				Error("Failed to terminate instance")
		}
	}

	// Delete the deployment from the database
	if err := s.store.Delete(deploymentID); err != nil {
		s.logger.WithError(err).WithField("deployment_id", deploymentID).
			Error("Failed to delete deployment record")
	}

	s.logger.WithField("deployment_id", deploymentID).Info("Deployment cleanup completed")
}

// 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
}
