package main

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

	socketio "github.com/googollee/go-socket.io"
)

// ---- Load ignore patterns ----
func loadIncludePatterns(file string) ([]string, error) {
	f, err := os.Open(file)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	var patterns []string
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		patterns = append(patterns, filepath.ToSlash(filepath.Clean(line)))
	}
	return patterns, scanner.Err()
}

// ---- Simple matcher ----
// (Later swap for github.com/sabhiram/go-gitignore lib for proper rules)
func shouldInclude(path string, includes []string) bool {
	cleanPath := filepath.ToSlash(filepath.Clean(path)) // normalize to forward slashes

	for _, pat := range includes {
		p := filepath.ToSlash(filepath.Clean(pat))

		// exact match (file or folder)
		if cleanPath == p {
			return true
		}

		// if p is a folder, include all paths under it
		if strings.HasPrefix(cleanPath, p+"/") {
			return true
		}
	}
	return false
}

// ---- Zip the repo ----
func zipProject(server *socketio.Server, srcDir, zipFile string, includes []string) error {
	outFile, err := os.Create(zipFile)
	if err != nil {
		return err
	}
	defer outFile.Close()

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

	err = filepath.WalkDir(srcDir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			server.BroadcastToRoom("/", "build", "buildlogs", fmt.Sprintf("❌ Error walking %s: %v", path, err))
			return err
		}
		if d.IsDir() {
			return nil
		}

		relPath, _ := filepath.Rel(srcDir, path)

		// only include files matching your patterns
		if !shouldInclude(relPath, includes) {
			return nil
		}

		file, err := os.Open(path)
		if err != nil {
			server.BroadcastToRoom("/", "build", "buildlogs", fmt.Sprintf("❌ Could not open: %s", relPath))
			return err
		}
		defer file.Close()

		info, _ := file.Stat()
		header, _ := zip.FileInfoHeader(info)
		header.Name = relPath

		writer, err := archive.CreateHeader(header)
		if err != nil {
			server.BroadcastToRoom("/", "build", "buildlogs", fmt.Sprintf("❌ Could not add header for: %s", relPath))
			return err
		}

		if _, err := io.Copy(writer, file); err != nil {
			server.BroadcastToRoom("/", "build", "buildlogs", fmt.Sprintf("❌ Error zipping: %s", relPath))
			return err
		}

		// ✅ Send progress back to clients
		server.BroadcastToRoom("/", "build", "buildlogs", fmt.Sprintf("📦 Added: %s", relPath))
		return nil
	})

	if err == nil {
		server.BroadcastToRoom("/", "build", "buildlogs", fmt.Sprintf("✅ Archive created: %s", zipFile))
		doCleanup(server, filepath.Dir(zipFile))
	}

	return err
}

func doCleanup(server *socketio.Server, dir string) {
	// read max builds from env
	maxStr := os.Getenv("MAX_BUILDS")
	if maxStr == "" {
		return
	}
	maxBuilds, err := strconv.Atoi(maxStr)
	if err != nil || maxBuilds <= 0 {
		return
	}

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

	var files []os.FileInfo
	for _, e := range entries {
		if !e.IsDir() && strings.HasSuffix(e.Name(), ".zip") {
			info, err := e.Info()
			if err == nil {
				files = append(files, info)
			}
		}
	}

	// Sort by modification time newest → oldest
	sort.Slice(files, func(i, j int) bool {
		return files[i].ModTime().After(files[j].ModTime())
	})

	// Delete extras
	if len(files) > maxBuilds {
		for _, f := range files[maxBuilds:] {
			toDelete := filepath.Join(dir, f.Name())
			if err := os.Remove(toDelete); err == nil {
				server.BroadcastToRoom("/", "build", "buildlogs",
					fmt.Sprintf("🧹 Removed old build: %s", f.Name()))
			}
		}
	}
}
