package service

import (
	"context"
	"fmt"

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

// addInfraComponent provisions an infrastructure component (postgres, redis, gcs).
// Unlike code components, these don't scaffold files - they provision resources.
func (s *ComponentService) addInfraComponent(ctx context.Context, projectID string, componentType domain.ComponentType, name string) (*domain.Component, error) {
	switch componentType {
	case domain.ComponentTypePostgres:
		return s.provisionPostgres(ctx, projectID, name)
	case domain.ComponentTypeRedis:
		return s.provisionRedis(ctx, projectID, name)
	case domain.ComponentTypeGCS:
		return s.provisionGCS(ctx, projectID, name)
	default:
		return nil, fmt.Errorf("%w: unknown infrastructure type %s", domain.ErrInvalidComponentType, componentType)
	}
}

// provisionPostgres provisions a PostgreSQL/CockroachDB database for the project.
func (s *ComponentService) provisionPostgres(ctx context.Context, projectID, name string) (*domain.Component, error) {
	if s.dbProvisioner == nil {
		return nil, fmt.Errorf("database provisioner not configured")
	}

	// Check if database already exists for this project
	existing, err := s.dbProvisioner.GetProjectDatabase(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("failed to check existing database: %w", err)
	}
	if existing != nil {
		return nil, fmt.Errorf("%w: postgres already provisioned for project %s", domain.ErrDuplicateComponent, projectID)
	}

	// Provision the database
	creds, err := s.dbProvisioner.CreateProjectDatabase(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("failed to provision database: %w", err)
	}

	// Store credentials if credential store is available
	log := logging.FromContext(ctx).WithService("component")
	if s.credentialStore != nil {
		if err := s.storeCredential(ctx, projectID, "database", "DATABASE_URL", creds.URL); err != nil {
			// Rollback on credential storage failure
			log.Error("failed to store DATABASE_URL, rolling back", logging.FieldError, err)
			if rollbackErr := s.dbProvisioner.DeleteProjectDatabase(ctx, projectID); rollbackErr != nil {
				log.Error("failed to rollback database", logging.FieldError, rollbackErr)
			}
			return nil, fmt.Errorf("failed to store credentials: %w", err)
		}
		if err := s.storeCredential(ctx, projectID, "database", "DATABASE_URL_STAGING", creds.URLStaging); err != nil {
			log.Warn("failed to store DATABASE_URL_STAGING", logging.FieldError, err)
		}
	}

	log.Info("postgres component provisioned",
		logging.FieldProjectID, projectID,
		"name", name,
		"database", creds.DatabaseName,
	)

	return &domain.Component{
		Type:         domain.ComponentTypePostgres,
		Name:         name,
		Path:         "infra/postgres",
		Port:         creds.Port,
		Template:     "postgres",
		Dependencies: []string{},
	}, nil
}

// provisionRedis provisions a Redis cache for the project.
func (s *ComponentService) provisionRedis(ctx context.Context, projectID, name string) (*domain.Component, error) {
	if s.cacheProvisioner == nil {
		return nil, fmt.Errorf("cache provisioner not configured")
	}

	// Check if cache already exists for this project
	existing, err := s.cacheProvisioner.GetProjectCache(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("failed to check existing cache: %w", err)
	}
	if existing != nil {
		return nil, fmt.Errorf("%w: redis already provisioned for project %s", domain.ErrDuplicateComponent, projectID)
	}

	// Provision the cache
	creds, err := s.cacheProvisioner.CreateProjectCache(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("failed to provision cache: %w", err)
	}

	// Store credentials if credential store is available
	log := logging.FromContext(ctx).WithService("component")
	if s.credentialStore != nil {
		if err := s.storeCredential(ctx, projectID, "cache", "REDIS_URL", creds.URL); err != nil {
			// Rollback on credential storage failure
			log.Error("failed to store REDIS_URL, rolling back", logging.FieldError, err)
			if rollbackErr := s.cacheProvisioner.DeleteProjectCache(ctx, projectID, false); rollbackErr != nil {
				log.Error("failed to rollback cache", logging.FieldError, rollbackErr)
			}
			return nil, fmt.Errorf("failed to store credentials: %w", err)
		}
		if err := s.storeCredential(ctx, projectID, "cache", "REDIS_URL_STAGING", creds.URLStaging); err != nil {
			log.Warn("failed to store REDIS_URL_STAGING", logging.FieldError, err)
		}
		if err := s.storeCredential(ctx, projectID, "cache", "REDIS_PREFIX", creds.Prefix); err != nil {
			log.Warn("failed to store REDIS_PREFIX", logging.FieldError, err)
		}
	}

	log.Info("redis component provisioned",
		logging.FieldProjectID, projectID,
		"name", name,
		"prefix", creds.Prefix,
	)

	return &domain.Component{
		Type:         domain.ComponentTypeRedis,
		Name:         name,
		Path:         "infra/redis",
		Port:         creds.Port,
		Template:     "redis",
		Dependencies: []string{},
	}, nil
}

// provisionGCS provisions a GCS bucket for the project.
func (s *ComponentService) provisionGCS(ctx context.Context, projectID, name string) (*domain.Component, error) {
	if s.storageProvisioner == nil {
		return nil, fmt.Errorf("storage provisioner not configured")
	}

	// Check if bucket already exists for this project
	existing, err := s.storageProvisioner.GetProjectBucket(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("failed to check existing bucket: %w", err)
	}
	if existing != nil {
		return nil, fmt.Errorf("%w: gcs already provisioned for project %s", domain.ErrDuplicateComponent, projectID)
	}

	// Provision the bucket
	creds, err := s.storageProvisioner.CreateProjectBucket(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("failed to provision storage: %w", err)
	}

	// Store credentials if credential store is available
	log := logging.FromContext(ctx).WithService("component")
	if s.credentialStore != nil {
		if err := s.storeCredential(ctx, projectID, "storage", domain.CredKeyGCSBucket, creds.BucketName); err != nil {
			// Rollback on credential storage failure
			log.Error("failed to store GCS_BUCKET, rolling back", logging.FieldError, err)
			if rollbackErr := s.storageProvisioner.DeleteProjectBucket(ctx, projectID, true); rollbackErr != nil {
				log.Error("failed to rollback bucket", logging.FieldError, rollbackErr)
			}
			return nil, fmt.Errorf("failed to store credentials: %w", err)
		}
		if err := s.storeCredential(ctx, projectID, "storage", domain.CredKeyGCSServiceAccountJSON, creds.ServiceAccountJSON); err != nil {
			log.Warn("failed to store GCS_SERVICE_ACCOUNT_JSON", logging.FieldError, err)
		}
	}

	log.Info("gcs component provisioned",
		logging.FieldProjectID, projectID,
		"name", name,
		"bucket", creds.BucketName,
	)

	return &domain.Component{
		Type:         domain.ComponentTypeGCS,
		Name:         name,
		Path:         "infra/gcs",
		Port:         0,
		Template:     "gcs",
		Dependencies: []string{},
	}, nil
}

// storeCredential stores a project-scoped credential.
func (s *ComponentService) storeCredential(ctx context.Context, projectID, category, key, value string) error {
	scopedKey := projectID + ":" + key
	return s.credentialStore.Set(ctx, domain.Credential{
		Key:      scopedKey,
		Value:    value,
		Category: category,
	})
}
