package cmd

import (
	"archive/zip"
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/spf13/cobra"

	"omixlab.com/mosis-portal/pkg/mospkg"
)

// BuildCmd returns the build command
func BuildCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "build [directory]",
		Short: "Create .mosis package",
		Long:  "Build a Mosis app package from the project directory.",
		Args:  cobra.MaximumNArgs(1),
		RunE:  runBuild,
	}

	cmd.Flags().StringP("output", "o", "", "Output path (default: dist/<package-id>-<version>.mosis)")
	cmd.Flags().Bool("no-compress", false, "Skip compression")

	return cmd
}

func runBuild(cmd *cobra.Command, args []string) error {
	dir := "."
	if len(args) > 0 {
		dir = args[0]
	}

	// Read manifest
	fmt.Println("Reading manifest.json...")
	manifestPath := filepath.Join(dir, "manifest.json")
	manifestData, err := os.ReadFile(manifestPath)
	if err != nil {
		return fmt.Errorf("manifest.json not found\n\nAre you in a Mosis project directory?")
	}

	manifest, err := mospkg.ParseManifest(manifestData)
	if err != nil {
		return fmt.Errorf("invalid manifest.json: %w", err)
	}

	// Validate manifest
	validationErrs := manifest.Validate()
	if len(validationErrs) > 0 {
		fmt.Println("Manifest validation failed:")
		for _, e := range validationErrs {
			fmt.Printf("  - %s\n", e.Message)
		}
		return fmt.Errorf("fix manifest errors before building")
	}

	fmt.Printf("Package: %s v%s (%d)\n", manifest.ID, manifest.Version, manifest.VersionCode)

	// Determine output path
	output, _ := cmd.Flags().GetString("output")
	if output == "" {
		output = filepath.Join(dir, "dist", fmt.Sprintf("%s-%s.mosis", manifest.ID, manifest.Version))
	}

	// Ensure dist directory exists
	if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil {
		return fmt.Errorf("create output directory: %w", err)
	}

	// Load .mosisignore patterns
	ignorePatterns := loadIgnorePatterns(dir)

	// Collect files
	fmt.Println("\nCollecting files...")
	files, err := collectFiles(dir, ignorePatterns)
	if err != nil {
		return fmt.Errorf("collect files: %w", err)
	}

	for _, f := range files {
		fmt.Printf("✓ %s\n", f)
	}

	// Create package
	fmt.Println("\nCreating package...")
	noCompress, _ := cmd.Flags().GetBool("no-compress")
	if err := createPackage(dir, files, output, !noCompress); err != nil {
		return fmt.Errorf("create package: %w", err)
	}

	info, _ := os.Stat(output)
	fmt.Printf("✓ Package created: %s (%.1f KB)\n", output, float64(info.Size())/1024)
	fmt.Println("\n⚠ Package is unsigned. Run 'mosis sign' before publishing.")

	return nil
}

func loadIgnorePatterns(dir string) []string {
	patterns := []string{
		".git",
		".git/**",
		".gitignore",
		"dist",
		"dist/**",
		".mosisignore",
		"*.md",
	}

	ignorePath := filepath.Join(dir, ".mosisignore")
	f, err := os.Open(ignorePath)
	if err != nil {
		return patterns
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		patterns = append(patterns, line)
	}

	return patterns
}

func collectFiles(dir string, ignorePatterns []string) ([]string, error) {
	var files []string

	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Get relative path
		relPath, _ := filepath.Rel(dir, path)
		if relPath == "." {
			return nil
		}

		// Convert to forward slashes for consistency
		relPath = filepath.ToSlash(relPath)

		// Check ignore patterns
		for _, pattern := range ignorePatterns {
			pattern = strings.TrimSuffix(pattern, "/")

			// Exact match
			if relPath == pattern {
				if info.IsDir() {
					return filepath.SkipDir
				}
				return nil
			}

			// Directory prefix match
			if strings.HasPrefix(relPath, pattern+"/") {
				if info.IsDir() {
					return filepath.SkipDir
				}
				return nil
			}

			// Glob pattern with **
			if strings.Contains(pattern, "**") {
				base := strings.Split(pattern, "**")[0]
				if strings.HasPrefix(relPath, base) {
					if info.IsDir() {
						return filepath.SkipDir
					}
					return nil
				}
			}

			// Wildcard extension match
			if strings.HasPrefix(pattern, "*.") {
				ext := strings.TrimPrefix(pattern, "*")
				if strings.HasSuffix(relPath, ext) {
					return nil
				}
			}
		}

		if !info.IsDir() {
			files = append(files, relPath)
		}

		return nil
	})

	return files, err
}

func createPackage(dir string, files []string, output string, compress bool) error {
	outFile, err := os.Create(output)
	if err != nil {
		return err
	}
	defer outFile.Close()

	writer := zip.NewWriter(outFile)
	defer writer.Close()

	for _, relPath := range files {
		fullPath := filepath.Join(dir, relPath)

		info, err := os.Stat(fullPath)
		if err != nil {
			return fmt.Errorf("stat %s: %w", relPath, err)
		}

		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return fmt.Errorf("header %s: %w", relPath, err)
		}

		// Use forward slashes in ZIP
		header.Name = filepath.ToSlash(relPath)

		if compress {
			header.Method = zip.Deflate
		} else {
			header.Method = zip.Store
		}

		w, err := writer.CreateHeader(header)
		if err != nil {
			return fmt.Errorf("create %s: %w", relPath, err)
		}

		f, err := os.Open(fullPath)
		if err != nil {
			return fmt.Errorf("open %s: %w", relPath, err)
		}

		if _, err := io.Copy(w, f); err != nil {
			f.Close()
			return fmt.Errorf("copy %s: %w", relPath, err)
		}
		f.Close()
	}

	return nil
}
