package services

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"git.linuxforward.com/byop/byop-engine/models"
	"github.com/sirupsen/logrus"
	"gopkg.in/yaml.v3"
)

// ComposeParser handles parsing and analyzing docker-compose.yml files
type ComposeParser struct {
	entry *logrus.Entry
}

// NewComposeParser creates a new ComposeParser instance
func NewComposeParser() *ComposeParser {
	return &ComposeParser{
		entry: logrus.WithField("service", "ComposeParser"),
	}
}

// ComposeFile represents the structure of a docker-compose.yml file
type ComposeFile struct {
	Version  string                        `yaml:"version,omitempty"`
	Services map[string]ComposeServiceYAML `yaml:"services"`
}

// ComposeService represents a service in docker-compose.yml
type ComposeServiceYAML struct {
	Image       string        `yaml:"image,omitempty"`
	Build       interface{}   `yaml:"build,omitempty"`       // Can be string or object
	Ports       []interface{} `yaml:"ports,omitempty"`       // Can be strings or objects
	Environment interface{}   `yaml:"environment,omitempty"` // Can be array or map
	Volumes     []interface{} `yaml:"volumes,omitempty"`
	Depends_On  []string      `yaml:"depends_on,omitempty"`
}

// ComposeServiceBuild represents the build configuration
type ComposeServiceBuild struct {
	Context    string `yaml:"context,omitempty"`
	Dockerfile string `yaml:"dockerfile,omitempty"`
}

// ParseComposeFile parses a docker-compose.yml file from a directory path
func (cp *ComposeParser) ParseComposeFile(projectDir, composePath string) (*models.AppImportReview, error) {
	fullComposePath := filepath.Join(projectDir, composePath)

	cp.entry.Infof("Parsing compose file at: %s", fullComposePath)

	// Check if compose file exists
	if _, err := os.Stat(fullComposePath); os.IsNotExist(err) {
		return &models.AppImportReview{
			Valid: false,
			Error: fmt.Sprintf("docker-compose.yml file not found at %s", composePath),
		}, nil
	}

	// Read the compose file
	content, err := os.ReadFile(fullComposePath)
	if err != nil {
		return &models.AppImportReview{
			Valid: false,
			Error: fmt.Sprintf("Failed to read docker-compose.yml: %v", err),
		}, nil
	}

	return cp.ParseComposeContent(string(content), filepath.Base(projectDir))
}

// ParseComposeContent parses docker-compose content directly from string
func (cp *ComposeParser) ParseComposeContent(content, projectName string) (*models.AppImportReview, error) {
	cp.entry.Info("Parsing compose content from string")

	var composeFile ComposeFile
	if err := yaml.Unmarshal([]byte(content), &composeFile); err != nil {
		cp.entry.Errorf("Failed to parse YAML: %v", err)
		return &models.AppImportReview{
			Valid: false,
			Error: fmt.Sprintf("Failed to parse docker-compose.yml: %v", err),
		}, nil
	}

	if len(composeFile.Services) == 0 {
		return &models.AppImportReview{
			Valid: false,
			Error: "No services found in docker-compose.yml",
		}, nil
	}

	// Convert compose services to our internal format
	services := make([]models.ComposeService, 0, len(composeFile.Services))

	for serviceName, serviceConfig := range composeFile.Services {
		composeService := models.ComposeService{
			Name: serviceName,
		}

		// Determine if this is a build or image service
		if serviceConfig.Build != nil {
			composeService.Source = "build"

			// Handle build configuration (can be string or object)
			switch build := serviceConfig.Build.(type) {
			case string:
				composeService.BuildContext = build
				composeService.Dockerfile = "Dockerfile" // default
			case map[string]interface{}:
				if context, ok := build["context"].(string); ok {
					composeService.BuildContext = context
				}
				if dockerfile, ok := build["dockerfile"].(string); ok {
					composeService.Dockerfile = dockerfile
				} else {
					composeService.Dockerfile = "Dockerfile" // default
				}
			}
		} else if serviceConfig.Image != "" {
			composeService.Source = "image"
			composeService.Image = serviceConfig.Image
		} else {
			cp.entry.Warnf("Service %s has neither build nor image configuration", serviceName)
			continue
		}

		// Extract ports
		for _, port := range serviceConfig.Ports {
			switch p := port.(type) {
			case string:
				composeService.Ports = append(composeService.Ports, p)
			case int:
				composeService.Ports = append(composeService.Ports, strconv.Itoa(p))
			case map[string]interface{}:
				if target, ok := p["target"].(int); ok {
					portStr := strconv.Itoa(target)
					if published, ok := p["published"].(int); ok {
						portStr = fmt.Sprintf("%d:%d", published, target)
					}
					composeService.Ports = append(composeService.Ports, portStr)
				}
			}
		}

		// Extract environment variables
		if serviceConfig.Environment != nil {
			composeService.Environment = make(map[string]string)

			switch env := serviceConfig.Environment.(type) {
			case []interface{}:
				for _, item := range env {
					if envStr, ok := item.(string); ok {
						parts := strings.SplitN(envStr, "=", 2)
						if len(parts) == 2 {
							composeService.Environment[parts[0]] = parts[1]
						}
					}
				}
			case map[string]interface{}:
				for key, value := range env {
					if valueStr, ok := value.(string); ok {
						composeService.Environment[key] = valueStr
					}
				}
			}
		}

		// Extract volumes
		for _, volume := range serviceConfig.Volumes {
			if volumeStr, ok := volume.(string); ok {
				composeService.Volumes = append(composeService.Volumes, volumeStr)
			}
		}

		services = append(services, composeService)
	}

	// Use provided project name or default
	appName := projectName
	if appName == "" {
		appName = "imported-app"
	}

	review := &models.AppImportReview{
		AppName:  appName,
		Services: services,
		Valid:    true,
	}

	cp.entry.Infof("Successfully parsed compose file with %d services", len(services))
	return review, nil
}
