package templates

import (
	"context"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strings"

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

// availableComponentTemplates lists all supported component templates.
var availableComponentTemplates = []port.ComponentTemplateInfo{
	{
		Type:        "service",
		Description: "Go API service using pkg/ shared packages",
		Stack:       "go",
		DefaultPort: 8080,
		DestDir:     "services",
	},
	{
		Type:        "worker",
		Description: "Go background worker for async job processing",
		Stack:       "go",
		DefaultPort: 0, // Workers don't expose ports
		DestDir:     "workers",
	},
	{
		Type:        "app-astro",
		Description: "Astro landing page with Tailwind CSS",
		Stack:       "astro",
		DefaultPort: 4321,
		DestDir:     "apps",
	},
	{
		Type:        "app-react",
		Description: "React SPA with Vite, TypeScript, and Tailwind",
		Stack:       "react",
		DefaultPort: 5173,
		DestDir:     "apps",
	},
	{
		Type:        "app-nextjs",
		Description: "Next.js 14 dashboard with App Router and design system",
		Stack:       "nextjs",
		DefaultPort: 3000,
		DestDir:     "apps",
	},
	{
		Type:        "cli",
		Description: "Go CLI tool using Cobra",
		Stack:       "go",
		DefaultPort: 0, // CLIs don't expose ports
		DestDir:     "cli",
	},
}

// GetComponentTemplate returns info about a specific component template.
func (p *Provider) GetComponentTemplate(ctx context.Context, componentType string) (*port.ComponentTemplateInfo, error) {
	// Check for context cancellation
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	for _, t := range availableComponentTemplates {
		if t.Type == componentType {
			result := t
			files, err := listComponentTemplateFiles(componentType)
			if err == nil {
				result.Files = files
			}
			return &result, nil
		}
	}
	return nil, fmt.Errorf("%w: component type %s", domain.ErrTemplateNotFound, componentType)
}

// ListComponentTemplates returns available component templates.
// If componentType is empty, returns all templates; otherwise filters by type.
func (p *Provider) ListComponentTemplates(ctx context.Context, componentType string) ([]port.ComponentTemplateInfo, error) {
	// Check for context cancellation
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	var result []port.ComponentTemplateInfo
	for _, t := range availableComponentTemplates {
		if componentType != "" && t.Type != componentType {
			continue
		}
		info := t
		files, err := listComponentTemplateFiles(t.Type)
		if err == nil {
			info.Files = files
		}
		result = append(result, info)
	}
	return result, nil
}

// GetComponentFiles returns the files for a component template with variables interpolated.
func (p *Provider) GetComponentFiles(ctx context.Context, componentType string, destPath string, vars map[string]string) ([]port.ComponentFile, error) {
	// Check for context cancellation
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	// Validate component type exists
	found := false
	for _, t := range availableComponentTemplates {
		if t.Type == componentType {
			found = true
			break
		}
	}
	if !found {
		return nil, fmt.Errorf("%w: component type %s", domain.ErrTemplateNotFound, componentType)
	}

	templateDir := "templates/components/" + componentType
	var files []port.ComponentFile

	err := fs.WalkDir(templatesFS, templateDir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}

		// Read file content
		content, err := templatesFS.ReadFile(path)
		if err != nil {
			return fmt.Errorf("failed to read component template file %s: %w", path, err)
		}

		// Interpolate variables
		interpolated := interpolateVars(string(content), vars)

		// Calculate relative path from component template root
		relPath, err := filepath.Rel(templateDir, path)
		if err != nil {
			return fmt.Errorf("failed to get relative path: %w", err)
		}

		// Strip .tmpl extension
		relPath = strings.TrimSuffix(relPath, ".tmpl")

		// Prepend destination path
		fullPath := filepath.Join(destPath, relPath)

		files = append(files, port.ComponentFile{
			Path:    fullPath,
			Content: interpolated,
		})

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to collect component template files: %w", err)
	}

	if len(files) == 0 {
		return nil, fmt.Errorf("component template %s contains no files", componentType)
	}

	p.logger.Debug("prepared component files",
		"component_type", componentType,
		"dest_path", destPath,
		"file_count", len(files),
	)

	return files, nil
}

// listComponentTemplateFiles returns the list of files in a component template.
func listComponentTemplateFiles(componentType string) ([]string, error) {
	templateDir := "templates/components/" + componentType
	var files []string

	err := fs.WalkDir(templatesFS, templateDir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}
		relPath, err := filepath.Rel(templateDir, path)
		if err != nil {
			return err
		}
		// Strip .tmpl extension for display
		relPath = strings.TrimSuffix(relPath, ".tmpl")
		files = append(files, relPath)
		return nil
	})

	return files, err
}

// GetComponentWoodpeckerStep returns the .woodpecker.step.yml content for a component.
// This is the CI step that should be inserted into the main .woodpecker.yml file.
func (p *Provider) GetComponentWoodpeckerStep(ctx context.Context, componentType string, vars map[string]string) (string, error) {
	// Check for context cancellation
	select {
	case <-ctx.Done():
		return "", ctx.Err()
	default:
	}

	stepPath := "templates/components/" + componentType + "/.woodpecker.step.yml.tmpl"
	content, err := templatesFS.ReadFile(stepPath)
	if err != nil {
		return "", fmt.Errorf("failed to read woodpecker step template: %w", err)
	}

	return interpolateVars(string(content), vars), nil
}

// RenderSkeletonToDir renders the monorepo skeleton template to a local directory.
// This is used for testing templates locally without needing Gitea.
func RenderSkeletonToDir(outputDir string, vars map[string]string) error {
	templateDir := "templates/skeleton"

	return fs.WalkDir(templatesFS, templateDir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}

		// Read file content
		content, err := templatesFS.ReadFile(path)
		if err != nil {
			return fmt.Errorf("failed to read template file %s: %w", path, err)
		}

		// Interpolate variables
		interpolated := interpolateVars(string(content), vars)

		// Calculate relative path from template root
		relPath, err := filepath.Rel(templateDir, path)
		if err != nil {
			return fmt.Errorf("failed to get relative path: %w", err)
		}

		// Strip .tmpl extension
		relPath = strings.TrimSuffix(relPath, ".tmpl")

		// Create output path
		outPath := filepath.Join(outputDir, relPath)

		// Create parent directories
		if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
			return fmt.Errorf("failed to create directory for %s: %w", outPath, err)
		}

		// Write file
		if err := os.WriteFile(outPath, []byte(interpolated), 0644); err != nil {
			return fmt.Errorf("failed to write file %s: %w", outPath, err)
		}

		return nil
	})
}

// RenderComponentToDir renders a component template to a local directory.
// The destPath is relative to outputDir (e.g., "services/my-api").
func RenderComponentToDir(outputDir, componentType, destPath string, vars map[string]string) error {
	// Validate component type exists
	found := false
	for _, t := range availableComponentTemplates {
		if t.Type == componentType {
			found = true
			break
		}
	}
	if !found {
		return fmt.Errorf("unknown component type: %s", componentType)
	}

	templateDir := "templates/components/" + componentType

	return fs.WalkDir(templatesFS, templateDir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}

		// Skip .woodpecker.step.yml.tmpl - it's for CI insertion, not file creation
		if strings.HasSuffix(path, ".woodpecker.step.yml.tmpl") {
			return nil
		}

		// Read file content
		content, err := templatesFS.ReadFile(path)
		if err != nil {
			return fmt.Errorf("failed to read component template file %s: %w", path, err)
		}

		// Interpolate variables
		interpolated := interpolateVars(string(content), vars)

		// Calculate relative path from component template root
		relPath, err := filepath.Rel(templateDir, path)
		if err != nil {
			return fmt.Errorf("failed to get relative path: %w", err)
		}

		// Strip .tmpl extension
		relPath = strings.TrimSuffix(relPath, ".tmpl")

		// Create output path under destPath
		outPath := filepath.Join(outputDir, destPath, relPath)

		// Create parent directories
		if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
			return fmt.Errorf("failed to create directory for %s: %w", outPath, err)
		}

		// Write file
		if err := os.WriteFile(outPath, []byte(interpolated), 0644); err != nil {
			return fmt.Errorf("failed to write file %s: %w", outPath, err)
		}

		return nil
	})
}
