// Package builders provides build implementations for different project types.
package builders

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/host-uk/core/pkg/build"
)

// TaskfileBuilder builds projects using Taskfile (https://taskfile.dev/).
// This is a generic builder that can handle any project type that has a Taskfile.
type TaskfileBuilder struct{}

// NewTaskfileBuilder creates a new Taskfile builder.
func NewTaskfileBuilder() *TaskfileBuilder {
	return &TaskfileBuilder{}
}

// Name returns the builder's identifier.
func (b *TaskfileBuilder) Name() string {
	return "taskfile"
}

// Detect checks if a Taskfile exists in the directory.
func (b *TaskfileBuilder) Detect(dir string) (bool, error) {
	// Check for Taskfile.yml, Taskfile.yaml, or Taskfile
	taskfiles := []string{
		"Taskfile.yml",
		"Taskfile.yaml",
		"Taskfile",
		"taskfile.yml",
		"taskfile.yaml",
	}

	for _, tf := range taskfiles {
		if _, err := os.Stat(filepath.Join(dir, tf)); err == nil {
			return true, nil
		}
	}
	return false, nil
}

// Build runs the Taskfile build task for each target platform.
func (b *TaskfileBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
	// Validate task CLI is available
	if err := b.validateTaskCli(); err != nil {
		return nil, err
	}

	// Create output directory
	outputDir := cfg.OutputDir
	if outputDir == "" {
		outputDir = filepath.Join(cfg.ProjectDir, "dist")
	}
	if err := os.MkdirAll(outputDir, 0755); err != nil {
		return nil, fmt.Errorf("taskfile.Build: failed to create output directory: %w", err)
	}

	var artifacts []build.Artifact

	// If no targets specified, just run the build task once
	if len(targets) == 0 {
		if err := b.runTask(ctx, cfg, "", ""); err != nil {
			return nil, err
		}

		// Try to find artifacts in output directory
		found := b.findArtifacts(outputDir)
		artifacts = append(artifacts, found...)
	} else {
		// Run build task for each target
		for _, target := range targets {
			if err := b.runTask(ctx, cfg, target.OS, target.Arch); err != nil {
				return nil, err
			}

			// Try to find artifacts for this target
			found := b.findArtifactsForTarget(outputDir, target)
			artifacts = append(artifacts, found...)
		}
	}

	return artifacts, nil
}

// runTask executes the Taskfile build task.
func (b *TaskfileBuilder) runTask(ctx context.Context, cfg *build.Config, goos, goarch string) error {
	// Build task command
	args := []string{"build"}

	// Pass variables if targets are specified
	if goos != "" {
		args = append(args, fmt.Sprintf("GOOS=%s", goos))
	}
	if goarch != "" {
		args = append(args, fmt.Sprintf("GOARCH=%s", goarch))
	}
	if cfg.OutputDir != "" {
		args = append(args, fmt.Sprintf("OUTPUT_DIR=%s", cfg.OutputDir))
	}
	if cfg.Name != "" {
		args = append(args, fmt.Sprintf("NAME=%s", cfg.Name))
	}
	if cfg.Version != "" {
		args = append(args, fmt.Sprintf("VERSION=%s", cfg.Version))
	}

	cmd := exec.CommandContext(ctx, "task", args...)
	cmd.Dir = cfg.ProjectDir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	// Set environment variables
	cmd.Env = os.Environ()
	if goos != "" {
		cmd.Env = append(cmd.Env, fmt.Sprintf("GOOS=%s", goos))
	}
	if goarch != "" {
		cmd.Env = append(cmd.Env, fmt.Sprintf("GOARCH=%s", goarch))
	}
	if cfg.OutputDir != "" {
		cmd.Env = append(cmd.Env, fmt.Sprintf("OUTPUT_DIR=%s", cfg.OutputDir))
	}
	if cfg.Name != "" {
		cmd.Env = append(cmd.Env, fmt.Sprintf("NAME=%s", cfg.Name))
	}
	if cfg.Version != "" {
		cmd.Env = append(cmd.Env, fmt.Sprintf("VERSION=%s", cfg.Version))
	}

	if goos != "" && goarch != "" {
		fmt.Printf("Running task build for %s/%s\n", goos, goarch)
	} else {
		fmt.Println("Running task build")
	}

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("taskfile.Build: task build failed: %w", err)
	}

	return nil
}

// findArtifacts searches for built artifacts in the output directory.
func (b *TaskfileBuilder) findArtifacts(outputDir string) []build.Artifact {
	var artifacts []build.Artifact

	entries, err := os.ReadDir(outputDir)
	if err != nil {
		return artifacts
	}

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		// Skip common non-artifact files
		name := entry.Name()
		if strings.HasPrefix(name, ".") || name == "CHECKSUMS.txt" {
			continue
		}

		artifacts = append(artifacts, build.Artifact{
			Path: filepath.Join(outputDir, name),
			OS:   "",
			Arch: "",
		})
	}

	return artifacts
}

// findArtifactsForTarget searches for built artifacts for a specific target.
func (b *TaskfileBuilder) findArtifactsForTarget(outputDir string, target build.Target) []build.Artifact {
	var artifacts []build.Artifact

	// 1. Look for platform-specific subdirectory: output/os_arch/
	platformSubdir := filepath.Join(outputDir, fmt.Sprintf("%s_%s", target.OS, target.Arch))
	if info, err := os.Stat(platformSubdir); err == nil && info.IsDir() {
		entries, _ := os.ReadDir(platformSubdir)
		for _, entry := range entries {
			if entry.IsDir() {
				// Handle .app bundles on macOS
				if target.OS == "darwin" && strings.HasSuffix(entry.Name(), ".app") {
					artifacts = append(artifacts, build.Artifact{
						Path: filepath.Join(platformSubdir, entry.Name()),
						OS:   target.OS,
						Arch: target.Arch,
					})
				}
				continue
			}
			// Skip hidden files
			if strings.HasPrefix(entry.Name(), ".") {
				continue
			}
			artifacts = append(artifacts, build.Artifact{
				Path: filepath.Join(platformSubdir, entry.Name()),
				OS:   target.OS,
				Arch: target.Arch,
			})
		}
		if len(artifacts) > 0 {
			return artifacts
		}
	}

	// 2. Look for files matching the target pattern in the root output dir
	patterns := []string{
		fmt.Sprintf("*-%s-%s*", target.OS, target.Arch),
		fmt.Sprintf("*_%s_%s*", target.OS, target.Arch),
		fmt.Sprintf("*-%s*", target.Arch),
	}

	for _, pattern := range patterns {
		matches, _ := filepath.Glob(filepath.Join(outputDir, pattern))
		for _, match := range matches {
			info, err := os.Stat(match)
			if err != nil || info.IsDir() {
				continue
			}

			artifacts = append(artifacts, build.Artifact{
				Path: match,
				OS:   target.OS,
				Arch: target.Arch,
			})
		}

		if len(artifacts) > 0 {
			break // Found matches, stop looking
		}
	}

	return artifacts
}

// validateTaskCli checks if the task CLI is available.
func (b *TaskfileBuilder) validateTaskCli() error {
	// Check PATH first
	if _, err := exec.LookPath("task"); err == nil {
		return nil
	}

	// Check common locations
	paths := []string{
		"/usr/local/bin/task",
		"/opt/homebrew/bin/task",
	}

	for _, p := range paths {
		if _, err := os.Stat(p); err == nil {
			return nil
		}
	}

	return fmt.Errorf("taskfile: task CLI not found. Install with: brew install go-task (macOS), go install github.com/go-task/task/v3/cmd/task@latest, or see https://taskfile.dev/installation/")
}
