package services

import (
	"context"
	"fmt"
	"os"
	"strings"

	"git.linuxforward.com/byop/byop-engine/dbstore"
	"git.linuxforward.com/byop/byop-engine/models"
	git "github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/sirupsen/logrus"
)

// AppImporter handles the import of applications from docker-compose files
type AppImporter struct {
	store       *dbstore.SQLiteStore
	parser      *ComposeParser
	builderSvc  *Builder
	entry       *logrus.Entry
	registryURL string
}

// NewAppImporter creates a new AppImporter instance
func NewAppImporter(store *dbstore.SQLiteStore, builderSvc *Builder, registryURL string) *AppImporter {
	return &AppImporter{
		store:       store,
		parser:      NewComposeParser(),
		builderSvc:  builderSvc,
		entry:       logrus.WithField("service", "AppImporter"),
		registryURL: registryURL,
	}
}

// ReviewComposeFile reviews a docker-compose file from a git repository
func (ai *AppImporter) ReviewComposeFile(ctx context.Context, req models.AppImportRequest) (*models.AppImportReview, error) {
	ai.entry.Infof("Reviewing compose file from %s (branch: %s, path: %s)", req.SourceURL, req.Branch, req.ComposePath)

	// Create temporary directory for cloning
	tempDir, err := os.MkdirTemp("", "byop-import-*")
	if err != nil {
		return nil, fmt.Errorf("failed to create temp directory: %w", err)
	}
	defer os.RemoveAll(tempDir)

	// Clone the repository
	if err := ai.cloneRepository(req.SourceURL, req.Branch, tempDir); err != nil {
		return &models.AppImportReview{
			Valid: false,
			Error: fmt.Sprintf("Failed to clone repository: %v", err),
		}, nil
	}

	// Set default compose path if not provided
	composePath := req.ComposePath
	if composePath == "" {
		composePath = "docker-compose.yml"
	}

	// Parse the compose file
	review, err := ai.parser.ParseComposeFile(tempDir, composePath)
	if err != nil {
		return nil, fmt.Errorf("failed to parse compose file: %w", err)
	}

	// Override app name if provided in request
	if req.AppName != "" {
		review.AppName = req.AppName
	}

	return review, nil
}

// CreateAppFromCompose creates an app and its components from a docker-compose import
func (ai *AppImporter) CreateAppFromCompose(ctx context.Context, req models.AppImportCreateRequest, userID uint) (*models.App, error) {
	ai.entry.Infof("Creating app '%s' from compose import for user %d", req.ConfirmedAppName, userID)

	// First, review the compose file to get the services
	review, err := ai.ReviewComposeFile(ctx, req.AppImportRequest)
	if err != nil {
		return nil, fmt.Errorf("failed to review compose file: %w", err)
	}

	if !review.Valid {
		return nil, fmt.Errorf("invalid compose file: %s", review.Error)
	}

	// Create the app
	app := &models.App{
		UserID:      userID,
		Name:        req.ConfirmedAppName,
		Description: fmt.Sprintf("Imported from docker-compose.yml from %s", req.SourceURL),
		Status:      models.AppStatusBuilding,
		Components:  "[]", // Will be updated after creating components
	}

	if err := ai.store.CreateApp(ctx, app); err != nil {
		return nil, fmt.Errorf("failed to create app: %w", err)
	}

	ai.entry.Infof("Created app ID %d: %s", app.ID, app.Name)

	// Create components for each service
	var componentIDs []uint
	for _, service := range review.Services {
		component, err := ai.createComponentFromService(ctx, app.ID, userID, service, req)
		if err != nil {
			ai.entry.Errorf("Failed to create component for service %s: %v", service.Name, err)
			// Continue with other services, don't fail the entire import
			continue
		}
		componentIDs = append(componentIDs, component.ID)
	}

	// Update app with component IDs
	if err := ai.updateAppComponents(ctx, app.ID, componentIDs); err != nil {
		ai.entry.Errorf("Failed to update app components: %v", err)
		// Non-fatal error, the app and components are still created
	}

	// Update app status
	if len(componentIDs) > 0 {
		app.Status = models.AppStatusBuilding
	} else {
		app.Status = models.AppStatusFailed
		app.ErrorMsg = "No components could be created from the compose file"
	}

	if err := ai.store.UpdateAppStatus(ctx, app.ID, app.Status, app.ErrorMsg); err != nil {
		ai.entry.Errorf("Failed to update app status: %v", err)
	}

	ai.entry.Infof("Successfully created app %s with %d components", app.Name, len(componentIDs))
	return app, nil
}

// createComponentFromService creates a component from a compose service
func (ai *AppImporter) createComponentFromService(ctx context.Context, appID, userID uint, service models.ComposeService, req models.AppImportCreateRequest) (*models.Component, error) {
	ai.entry.Infof("Creating component for service: %s (source: %s)", service.Name, service.Source)

	// Determine component type based on service configuration
	componentType := "web"
	if len(service.Ports) == 0 {
		componentType = "worker"
	}

	// Resolve build context for docker-compose imports
	buildContext := service.BuildContext
	if buildContext == "" {
		buildContext = "."
	}
	// Clean up the build context path to remove ./ prefix and ensure it's relative
	buildContext = strings.TrimPrefix(buildContext, "./")

	// Create component
	component := &models.Component{
		UserID:         userID,
		Name:           service.Name,
		Description:    fmt.Sprintf("Service from docker-compose: %s", service.Name),
		Type:           componentType,
		Status:         "pending",
		Repository:     req.SourceURL,
		Branch:         req.Branch,
		SourceType:     "docker-compose",
		ServiceName:    service.Name,
		BuildContext:   buildContext,
		DockerfilePath: service.Dockerfile,
	}

	if err := ai.store.CreateComponent(ctx, component); err != nil {
		return nil, fmt.Errorf("failed to create component: %w", err)
	}

	ai.entry.Infof("Created component ID %d: %s", component.ID, component.Name)

	// Handle the component based on its source type
	switch service.Source {
	case "build":
		// Queue a build job for this component
		if err := ai.queueBuildJob(ctx, component, service, req); err != nil {
			ai.entry.Errorf("Failed to queue build job for component %d: %v", component.ID, err)
			ai.store.UpdateComponentStatus(ctx, component.ID, "failed", fmt.Sprintf("Failed to queue build: %v", err))
		}
	case "image":
		// Mark component as ready and set image info
		ai.store.UpdateComponentStatus(ctx, component.ID, "ready", "")
		ai.store.UpdateComponentImageInfo(ctx, component.ID, "latest", service.Image)
		ai.entry.Infof("Component %d marked as ready with image: %s", component.ID, service.Image)
	default:
		ai.entry.Warnf("Unknown service source type: %s", service.Source)
		ai.store.UpdateComponentStatus(ctx, component.ID, "failed", "Unknown service source type")
	}

	return component, nil
}

// queueBuildJob queues a build job for a component that needs to be built
func (ai *AppImporter) queueBuildJob(ctx context.Context, component *models.Component, service models.ComposeService, req models.AppImportCreateRequest) error {
	// Use the build context already stored in the component
	buildContext := component.BuildContext
	if buildContext == "" {
		buildContext = "."
	}

	ai.entry.Infof("Using build context for service %s: %s", service.Name, buildContext)

	// Prepare build request
	buildReq := models.BuildRequest{
		ComponentID:  component.ID,
		SourceURL:    req.SourceURL,
		Version:      req.Branch,
		ImageName:    fmt.Sprintf("byop-component-%d", component.ID),
		RegistryURL:  ai.registryURL,
		BuildContext: buildContext,
		Dockerfile:   component.DockerfilePath,
		Source:       "docker-compose",
	}

	// Set default dockerfile if not specified
	if buildReq.Dockerfile == "" {
		buildReq.Dockerfile = "Dockerfile"
	}

	_, err := ai.builderSvc.QueueBuildJob(ctx, buildReq)
	if err != nil {
		return fmt.Errorf("failed to queue build job: %w", err)
	}

	ai.entry.Infof("Queued build job for component %d (service: %s)", component.ID, service.Name)
	return nil
}

// updateAppComponents updates the app's components field with the component IDs
func (ai *AppImporter) updateAppComponents(ctx context.Context, appID uint, componentIDs []uint) error {
	// Convert component IDs to JSON string
	componentsJSON := "["
	for i, id := range componentIDs {
		if i > 0 {
			componentsJSON += ","
		}
		componentsJSON += fmt.Sprintf("%d", id)
	}
	componentsJSON += "]"

	// Update the app in the database
	app, err := ai.store.GetAppByID(ctx, appID)
	if err != nil {
		return fmt.Errorf("failed to get app: %w", err)
	}

	app.Components = componentsJSON
	if err := ai.store.UpdateApp(ctx, app); err != nil {
		return fmt.Errorf("failed to update app components: %w", err)
	}

	return nil
}

// cloneRepository clones a Git repository to the specified directory
func (ai *AppImporter) cloneRepository(repoURL, branch, targetDir string) error {
	ai.entry.Infof("Cloning repository %s (branch: %s) to %s", repoURL, branch, targetDir)

	// Clone options
	cloneOptions := &git.CloneOptions{
		URL:      repoURL,
		Progress: nil, // We could add progress tracking here
	}

	// Set branch if specified
	if branch != "" {
		cloneOptions.ReferenceName = plumbing.NewBranchReferenceName(branch)
		cloneOptions.SingleBranch = true
	}

	// Clone the repository
	_, err := git.PlainClone(targetDir, false, cloneOptions)
	if err != nil {
		return fmt.Errorf("failed to clone repository: %w", err)
	}

	ai.entry.Infof("Successfully cloned repository to %s", targetDir)
	return nil
}
