package dbstore

import (
	"encoding/json"
	"fmt"

	"git.linuxforward.com/byop/byop-engine/dbmanager"
	"git.linuxforward.com/byop/byop-engine/models"
	"github.com/google/uuid"
	"gorm.io/gorm"
)

// DeploymentStore handles database operations for deployments
type DeploymentStore struct {
	db *gorm.DB
}

// NewDeploymentStore creates a new DeploymentStore
func NewDeploymentStore(dbManager dbmanager.DbManager) *DeploymentStore {
	return &DeploymentStore{
		db: dbManager.GetDB(),
	}
}

// Create creates a new deployment
func (ds *DeploymentStore) Create(deployment *models.Deployment) error {
	// Generate ID if not provided
	if deployment.ID == "" {
		deployment.ID = uuid.New().String()
	}

	// Ensure logs, metrics, and alerts config are properly JSON serialized
	if err := ds.serializeConfigFields(deployment); err != nil {
		return fmt.Errorf("failed to serialize config fields: %w", err)
	}

	// Create deployment in a transaction to handle deployed apps
	return ds.db.Transaction(func(tx *gorm.DB) error {
		// Create the deployment
		if err := tx.Create(deployment).Error; err != nil {
			return err
		}

		// Create any deployed apps in the same transaction
		for i := range deployment.DeployedApps {
			app := &deployment.DeployedApps[i]

			// Ensure each deployed app has an ID
			if app.ID == "" {
				app.ID = uuid.New().String()
			}

			// Set the deployment ID (ensure relationship is maintained)
			app.DeploymentID = deployment.ID

			// Create the deployed app
			if err := tx.Create(app).Error; err != nil {
				return err
			}

			// Handle resources if provided
			if app.Resources != (models.ResourceAllocation{}) {
				resource := models.DeployedAppResource{
					ID:            uuid.New().String(),
					DeployedAppID: app.ID,
					CPU:           app.Resources.CPU,
					CPUUsage:      app.Resources.CPUUsage,
					Memory:        app.Resources.Memory,
					MemoryUsage:   app.Resources.MemoryUsage,
					Storage:       app.Resources.Storage,
					StorageUsage:  app.Resources.StorageUsage,
				}

				if err := tx.Create(&resource).Error; err != nil {
					return err
				}
			}
		}

		return nil
	})
}

// GetByID retrieves a deployment by ID
func (ds *DeploymentStore) GetByID(id string) (*models.Deployment, error) {
	var deployment models.Deployment

	// Get deployment with all related deployed apps
	err := ds.db.
		Preload("DeployedApps").
		First(&deployment, "id = ?", id).Error

	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil // No deployment found
		}
		return nil, fmt.Errorf("failed to get deployment: %w", err)
	}

	// Load resources for each deployed app
	for i, app := range deployment.DeployedApps {
		var resource models.DeployedAppResource
		if err := ds.db.Where("deployed_app_id = ?", app.ID).First(&resource).Error; err != nil {
			if err != gorm.ErrRecordNotFound {
				return nil, fmt.Errorf("failed to get resources for deployed app: %w", err)
			}
		} else {
			deployment.DeployedApps[i].Resources = models.ResourceAllocation{
				CPU:          resource.CPU,
				CPUUsage:     resource.CPUUsage,
				Memory:       resource.Memory,
				MemoryUsage:  resource.MemoryUsage,
				Storage:      resource.Storage,
				StorageUsage: resource.StorageUsage,
			}
		}
	}

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

	return &deployment, nil
}

// Update updates an existing deployment
func (ds *DeploymentStore) Update(deployment *models.Deployment) error {
	// Ensure logs, metrics, and alerts config are properly JSON serialized
	if err := ds.serializeConfigFields(deployment); err != nil {
		return fmt.Errorf("failed to serialize config fields: %w", err)
	}

	// Use transaction to handle deployment and deployed apps
	return ds.db.Transaction(func(tx *gorm.DB) error {
		// Update the deployment
		if err := tx.Save(deployment).Error; err != nil {
			return err
		}

		// Handle deployed apps - this is trickier as we need to compare with existing apps
		var existingApps []models.DeployedApp
		if err := tx.Where("deployment_id = ?", deployment.ID).Find(&existingApps).Error; err != nil {
			return err
		}

		// Create a map of existing app IDs for quick lookup
		existingAppMap := make(map[string]bool)
		for _, app := range existingApps {
			existingAppMap[app.ID] = true
		}

		// Process each app in the updated deployment
		for i := range deployment.DeployedApps {
			app := &deployment.DeployedApps[i]

			// If app has ID and exists, update it
			if app.ID != "" && existingAppMap[app.ID] {
				if err := tx.Save(app).Error; err != nil {
					return err
				}
				delete(existingAppMap, app.ID)
			} else {
				// New app, create it
				if app.ID == "" {
					app.ID = uuid.New().String()
				}
				app.DeploymentID = deployment.ID
				if err := tx.Create(app).Error; err != nil {
					return err
				}
			}

			// Handle resources
			if app.Resources != (models.ResourceAllocation{}) {
				var resource models.DeployedAppResource
				result := tx.Where("deployed_app_id = ?", app.ID).First(&resource)
				if result.Error != nil && result.Error != gorm.ErrRecordNotFound {
					return result.Error
				}

				if result.Error == gorm.ErrRecordNotFound {
					// Create new resource
					resource = models.DeployedAppResource{
						ID:            uuid.New().String(),
						DeployedAppID: app.ID,
						CPU:           app.Resources.CPU,
						CPUUsage:      app.Resources.CPUUsage,
						Memory:        app.Resources.Memory,
						MemoryUsage:   app.Resources.MemoryUsage,
						Storage:       app.Resources.Storage,
						StorageUsage:  app.Resources.StorageUsage,
					}
					if err := tx.Create(&resource).Error; err != nil {
						return err
					}
				} else {
					// Update existing resource
					resource.CPU = app.Resources.CPU
					resource.CPUUsage = app.Resources.CPUUsage
					resource.Memory = app.Resources.Memory
					resource.MemoryUsage = app.Resources.MemoryUsage
					resource.Storage = app.Resources.Storage
					resource.StorageUsage = app.Resources.StorageUsage
					if err := tx.Save(&resource).Error; err != nil {
						return err
					}
				}
			}
		}

		// Delete any apps that are no longer part of the deployment
		for appID := range existingAppMap {
			if err := tx.Delete(&models.DeployedApp{}, "id = ?", appID).Error; err != nil {
				return err
			}
			// Delete associated resources
			if err := tx.Delete(&models.DeployedAppResource{}, "deployed_app_id = ?", appID).Error; err != nil && err != gorm.ErrRecordNotFound {
				return err
			}
		}

		return nil
	})
}

// Delete deletes a deployment by ID
func (ds *DeploymentStore) Delete(id string) error {
	return ds.db.Transaction(func(tx *gorm.DB) error {
		// Delete associated DeployedAppResources
		var deployedApps []models.DeployedApp
		if err := tx.Where("deployment_id = ?", id).Find(&deployedApps).Error; err != nil {
			return err
		}

		for _, app := range deployedApps {
			if err := tx.Delete(&models.DeployedAppResource{}, "deployed_app_id = ?", app.ID).Error; err != nil && err != gorm.ErrRecordNotFound {
				return err
			}
		}

		// Delete deployed apps
		if err := tx.Delete(&models.DeployedApp{}, "deployment_id = ?", id).Error; err != nil && err != gorm.ErrRecordNotFound {
			return err
		}

		// Delete the deployment itself
		return tx.Delete(&models.Deployment{}, "id = ?", id).Error
	})
}

// List retrieves all deployments with optional filtering
func (ds *DeploymentStore) List(filter map[string]interface{}) ([]*models.Deployment, error) {
	var deployments []*models.Deployment

	// Build query from filters
	query := ds.db.Preload("DeployedApps")
	if filter != nil {
		for key, value := range filter {
			query = query.Where(key+" = ?", value)
		}
	}

	// Execute query
	if err := query.Find(&deployments).Error; err != nil {
		return nil, fmt.Errorf("failed to list deployments: %w", err)
	}

	// Load resources and deserialize config for each deployment
	for i, deployment := range deployments {
		// Load resources for each deployed app
		for j, app := range deployment.DeployedApps {
			var resource models.DeployedAppResource
			if err := ds.db.Where("deployed_app_id = ?", app.ID).First(&resource).Error; err != nil {
				if err != gorm.ErrRecordNotFound {
					return nil, fmt.Errorf("failed to get resources for deployed app: %w", err)
				}
			} else {
				deployments[i].DeployedApps[j].Resources = models.ResourceAllocation{
					CPU:          resource.CPU,
					CPUUsage:     resource.CPUUsage,
					Memory:       resource.Memory,
					MemoryUsage:  resource.MemoryUsage,
					Storage:      resource.Storage,
					StorageUsage: resource.StorageUsage,
				}
			}
		}

		// Deserialize config fields
		if err := ds.deserializeConfigFields(deployments[i]); err != nil {
			return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
		}
	}

	return deployments, nil
}

// GetByClientID retrieves deployments for a specific client
func (ds *DeploymentStore) GetByClientID(clientID string) ([]*models.Deployment, error) {
	return ds.List(map[string]interface{}{"client_id": clientID})
}

// GetByUserID retrieves deployments created by a specific user
func (ds *DeploymentStore) GetByUserID(userID string) ([]*models.Deployment, error) {
	return ds.List(map[string]interface{}{"created_by": userID})
}

// GetByTemplateID retrieves deployments based on a specific template
func (ds *DeploymentStore) GetByTemplateID(templateID string) ([]*models.Deployment, error) {
	return ds.List(map[string]interface{}{"template_id": templateID})
}

// serializeConfigFields serializes JSON config fields to strings
func (ds *DeploymentStore) serializeConfigFields(deployment *models.Deployment) error {
	// Serialize logs config if provided
	if deployment.LogsConfig == "" {
		logsConfig := models.LogConfiguration{
			Enabled:       true,
			RetentionDays: 7,
		}
		logsConfigBytes, err := json.Marshal(logsConfig)
		if err != nil {
			return err
		}
		deployment.LogsConfig = string(logsConfigBytes)
	}

	// Serialize metrics config if provided
	if deployment.MetricsConfig == "" {
		metricsConfig := models.MetricsConfiguration{
			Enabled:       true,
			RetentionDays: 30,
		}
		metricsConfigBytes, err := json.Marshal(metricsConfig)
		if err != nil {
			return err
		}
		deployment.MetricsConfig = string(metricsConfigBytes)
	}

	// Serialize alerts config if provided
	if deployment.AlertsConfig == "" {
		alertsConfig := []models.AlertConfiguration{}
		alertsConfigBytes, err := json.Marshal(alertsConfig)
		if err != nil {
			return err
		}
		deployment.AlertsConfig = string(alertsConfigBytes)
	}

	return nil
}

// deserializeConfigFields deserializes JSON config fields from strings
func (ds *DeploymentStore) deserializeConfigFields(deployment *models.Deployment) error {
	// No need to deserialize in the store, as these fields are stored as strings
	// in the database and are deserialized as needed by the service layer
	return nil
}
