package service

import (
	"fmt"
	"regexp"
	"strings"

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

var (
	reDeployStep = regexp.MustCompile(`(?m)^deploy-([a-zA-Z0-9_-]+):`)
	reDependsOn  = regexp.MustCompile(`depends_on:\s*\[([^\]]*)\]`)
)

// updateProcfile adds an entry for the component.
func (s *ComponentService) updateProcfile(existing string, componentType domain.ComponentType, componentName, componentPath string, _ int) string {
	var entry string

	switch componentType {
	case domain.ComponentTypeService:
		entry = fmt.Sprintf("%s: cd %s && make run", componentName, componentPath)
	case domain.ComponentTypeWorker:
		entry = fmt.Sprintf("%s: cd %s && make run", componentName, componentPath)
	case domain.ComponentTypeAppAstro, domain.ComponentTypeAppReact:
		entry = fmt.Sprintf("%s: cd %s && npm run dev", componentName, componentPath)
	case domain.ComponentTypeCLI:
		// CLIs don't run as processes
		return existing
	}

	// Add the entry before any empty line at the end, or at the end
	lines := strings.Split(strings.TrimRight(existing, "\n"), "\n")
	lines = append(lines, entry)
	return strings.Join(lines, "\n") + "\n"
}

// updateGoWork adds a use directive for Go components.
func (s *ComponentService) updateGoWork(existing, componentPath string) string {
	useLine := fmt.Sprintf("use ./%s", componentPath)

	// Check if already present
	if strings.Contains(existing, useLine) {
		return existing
	}

	// Find where to insert: after the last 'use' line or after 'go X.XX'
	lines := strings.Split(existing, "\n")
	insertIdx := len(lines) - 1

	// Find the last use statement
	for i := len(lines) - 1; i >= 0; i-- {
		trimmed := strings.TrimSpace(lines[i])
		if strings.HasPrefix(trimmed, "use ") {
			insertIdx = i + 1
			break
		}
		if strings.HasPrefix(trimmed, "go ") {
			insertIdx = i + 1
		}
	}

	// Insert the new use line
	newLines := make([]string, 0, len(lines)+1)
	newLines = append(newLines, lines[:insertIdx]...)
	newLines = append(newLines, useLine)
	newLines = append(newLines, lines[insertIdx:]...)

	return strings.Join(newLines, "\n")
}

// updateWoodpeckerYml inserts the component step at the COMPONENT_STEPS_BELOW marker
// and updates build-complete's depends_on to include the new deploy step.
func (s *ComponentService) updateWoodpeckerYml(existing, stepYaml string) string {
	marker := "# COMPONENT_STEPS_BELOW"

	if !strings.Contains(existing, marker) {
		log := logging.Default().WithService("component")
		log.Warn("COMPONENT_STEPS_BELOW marker not found in .woodpecker.yml")
		return existing
	}

	// Indent the step YAML properly (2 spaces for YAML steps)
	var sb strings.Builder
	lines := strings.Split(strings.TrimSpace(stepYaml), "\n")
	for _, line := range lines {
		sb.WriteString("  ")
		sb.WriteString(line)
		sb.WriteString("\n")
	}

	// Insert after the marker
	result := strings.Replace(existing, marker, marker+"\n\n"+strings.TrimRight(sb.String(), "\n"), 1)

	// Extract deploy step name from the step YAML (e.g., "deploy-studio-api:")
	deployStep := extractDeployStepName(stepYaml)
	if deployStep != "" {
		result = addBuildCompleteDep(result, deployStep)
	}

	return result
}

// extractDeployStepName finds the deploy-{name}: key in a step YAML block.
func extractDeployStepName(stepYaml string) string {
	match := reDeployStep.FindStringSubmatch(stepYaml)
	if len(match) >= 2 {
		return "deploy-" + match[1]
	}
	return ""
}

// addBuildCompleteDep appends a step name to build-complete's depends_on line
// identified by the BUILD_COMPLETE_DEPS marker.
func addBuildCompleteDep(yml, stepName string) string {
	const depsMarker = "# BUILD_COMPLETE_DEPS"

	lines := strings.Split(yml, "\n")
	for i, line := range lines {
		if !strings.Contains(line, depsMarker) {
			continue
		}

		// Parse existing depends_on array from the line
		// Format: "    depends_on: [preflight, deploy-foo] # BUILD_COMPLETE_DEPS"
		match := reDependsOn.FindStringSubmatch(line)
		if len(match) < 2 {
			break
		}

		// Parse existing deps
		existing := strings.TrimSpace(match[1])
		var deps []string
		for _, d := range strings.Split(existing, ",") {
			d = strings.TrimSpace(d)
			if d != "" {
				deps = append(deps, d)
			}
		}

		// Append new dep if not already present
		for _, d := range deps {
			if d == stepName {
				return yml
			}
		}
		deps = append(deps, stepName)

		// Reconstruct the line preserving indentation
		indent := line[:len(line)-len(strings.TrimLeft(line, " \t"))]
		newLine := fmt.Sprintf("%sdepends_on: [%s] %s", indent, strings.Join(deps, ", "), depsMarker)
		lines[i] = newLine
		return strings.Join(lines, "\n")
	}

	return yml
}

// updateClaudeMd adds the component to the routing table.
func (s *ComponentService) updateClaudeMd(existing string, componentType domain.ComponentType, componentName, componentPath string) string {
	// Find the "## Components" section and add entry
	marker := "<!-- Components will be listed here as they're added -->"

	var description string
	switch componentType {
	case domain.ComponentTypeService:
		description = "API service"
	case domain.ComponentTypeWorker:
		description = "Background worker"
	case domain.ComponentTypeAppAstro:
		description = "Astro app"
	case domain.ComponentTypeAppReact:
		description = "React app"
	case domain.ComponentTypeCLI:
		description = "CLI tool"
	}

	entry := fmt.Sprintf("| **%s** | %s | `%s/` |", componentName, description, componentPath)

	if strings.Contains(existing, marker) {
		// First component - replace the marker with a table
		table := fmt.Sprintf(`| Component | Type | Path |
|-----------|------|------|
%s
`, entry)
		return strings.Replace(existing, marker, table, 1)
	}

	// Add to existing table - find the last table row in ## Components section
	lines := strings.Split(existing, "\n")
	inComponents := false
	insertIdx := -1

	for i, line := range lines {
		if strings.HasPrefix(line, "## Components") {
			inComponents = true
			continue
		}
		if inComponents && strings.HasPrefix(line, "## ") {
			// End of Components section
			insertIdx = i
			break
		}
		if inComponents && strings.HasPrefix(line, "|") {
			insertIdx = i + 1
		}
	}

	if insertIdx > 0 {
		// Insert the new entry
		newLines := make([]string, 0, len(lines)+1)
		newLines = append(newLines, lines[:insertIdx]...)
		newLines = append(newLines, entry)
		newLines = append(newLines, lines[insertIdx:]...)
		return strings.Join(newLines, "\n")
	}

	return existing
}

// removeProcfileEntry removes a component entry from the Procfile.
func (s *ComponentService) removeProcfileEntry(procfile, componentName string) string {
	var lines []string
	for _, line := range strings.Split(procfile, "\n") {
		if !strings.HasPrefix(strings.TrimSpace(line), componentName+":") {
			lines = append(lines, line)
		}
	}
	return strings.Join(lines, "\n")
}

// removeGoWorkEntry removes a use directive from go.work.
func (s *ComponentService) removeGoWorkEntry(goWork, componentPath string) string {
	useLine := "use ./" + componentPath
	var lines []string
	for _, line := range strings.Split(goWork, "\n") {
		if strings.TrimSpace(line) != useLine {
			lines = append(lines, line)
		}
	}
	return strings.Join(lines, "\n")
}
