package main

import (
	"archive/zip"
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

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

func UpdateApp(server *socketio.Server) <-chan string {
	updates := make(chan string)
	exePath, _ := os.Executable()
	exeDir := filepath.Dir(exePath)
	rootDir := filepath.Join(exeDir, "..")

	entries, err := os.ReadDir(rootDir)
	if err != nil {
		msg := fmt.Sprintf("failed to read root dir %s: %v", rootDir, err)
		updates <- msg
		server.BroadcastToRoom("/", "update", "updateLogs", msg)

		return updates
	}

	var zips []string
	for _, e := range entries {
		if !e.IsDir() && filepath.Ext(e.Name()) == ".zip" {
			zips = append(zips, filepath.Join(rootDir, e.Name()))
		}
	}

	go func() {
		defer close(updates)

		switch len(zips) {
		case 0:
			msg := "No zip files found in root directory"
			updates <- msg
			server.BroadcastToRoom("/", "update", "updateLogs", msg)
		case 1:
			var oldService string
			var newService string
			port := os.Getenv("PORT")

			if port == "8081" {
				oldService = "LSTV2_2"
				newService = "lst_app_2"
			} else {
				oldService = "LSTV2"
				newService = "lst_app"
			}

			version, err := extractVersion(filepath.Base(zips[0]))
			if err != nil {
				msg := fmt.Sprintf("could not parse version: %v", err)
				updates <- msg
				server.BroadcastToRoom("/", "update", "updateLogs", msg)
			}

			msg := "Stopping the services"
			updates <- msg
			server.BroadcastToRoom("/", "update", "updateLogs", msg)
			// 1. Stop services + pool
			stopService(oldService)
			time.Sleep(1 * time.Second)
			stopService(newService)
			//stopAppPool("LogisticsSupportTool")
			time.Sleep(1 * time.Second)

			msg = "Checking cleanup of files"
			updates <- msg
			server.BroadcastToRoom("/", "update", "updateLogs", msg)

			if version%10 == 0 {
				msg = fmt.Sprintf("Release %d is a cleanup milestone — cleaning root", version)
				updates <- msg

				server.BroadcastToRoom("/", "update", "updateLogs", msg)
				if err := cleanupRoot(rootDir); err != nil {
					msg = fmt.Sprintf("cleanup failed: %v", err)
					updates <- msg
					server.BroadcastToRoom("/", "update", "updateLogs", msg)
				}
			}

			msg = "Unzipping New Build"
			updates <- msg
			server.BroadcastToRoom("/", "update", "updateLogs", msg)
			if err := Unzip(zips[0], rootDir); err != nil {
				msg = fmt.Sprintf("unzip failed: %v", err)
				updates <- msg
				server.BroadcastToRoom("/", "update", "updateLogs", msg)
			}

			msg = "Running App Update"
			updates <- msg
			server.BroadcastToRoom("/", "update", "updateLogs", msg)
			if err := runNPMInstall(rootDir, ""); err != nil {
				server.BroadcastToRoom("/", "update", "updateLogs", fmt.Sprintf("npm install failed: %v", err))
			}

			if err := runNPMInstall(rootDir, "lstV2"); err != nil {
				server.BroadcastToRoom("/", "update", "updateLogs", fmt.Sprintf("npm install failed: %v", err))
			}

			msg = "Running DB Migrations"
			updates <- msg
			server.BroadcastToRoom("/", "update", "updateLogs", msg)
			if err := runNPMMigrate(rootDir, ""); err != nil {
				msg = fmt.Sprintf("npm migrate failed: %v", err)
				updates <- msg
				server.BroadcastToRoom("/", "update", "updateLogs", msg)
			}

			if err := runNPMMigrate(rootDir, "lstV2"); err != nil {
				msg = fmt.Sprintf("npm migrate failed: %v", err)
				updates <- msg
				server.BroadcastToRoom("/", "update", "updateLogs", msg)
			}

			msg = "Starting Services back up"
			updates <- msg
			server.BroadcastToRoom("/", "update", "updateLogs", msg)
			startService(newService)
			time.Sleep(2 * time.Second)
			startService(oldService)
			time.Sleep(2 * time.Second)
			//startAppPool("LogisticsSupportTool")

			if err := os.Remove(zips[0]); err != nil {
				msg = fmt.Sprintf("warning: failed to delete zip %s: %v", zips[0], err)
				updates <- msg
				server.BroadcastToRoom("/", "update", "updateLogs", msg)
			} else {
				msg = fmt.Sprintf("Deleted zip %s", zips[0])
				updates <- msg
				server.BroadcastToRoom("/", "update", "updateLogs", msg)
			}

			msg = "Deployment finished successfully"
			updates <- msg

			// remove the prefix
			cleaned := strings.Replace(strconv.Itoa(version), "release-", "", 1)

			num, err := strconv.Atoi(cleaned)

			if err != nil {
				fmt.Println("Error parsing number:", err)
			}
			pkg.UpdateServerStats(int64(num))

			server.BroadcastToRoom("/", "update", "updateLogs", msg)
		default:
			msg := fmt.Sprintf("Error: too many zip files in root: %v\n", zips)
			updates <- msg
			server.BroadcastToRoom("/", "update", "updateLogs", msg)

		}

		updates <- "done"
	}()

	return updates
}

func Unzip(srcZip, destDir string) error {

	r, err := zip.OpenReader(srcZip)
	if err != nil {
		return fmt.Errorf("opening zip: %w", err)
	}
	defer r.Close()

	for _, f := range r.File {
		// Build the destination path for each file
		fpath := filepath.Join(destDir, f.Name)

		// Check for ZipSlip (directory traversal)
		if !strings.HasPrefix(fpath, filepath.Clean(destDir)+string(os.PathSeparator)) {
			return fmt.Errorf("illegal file path: %s", fpath)
		}

		if f.FileInfo().IsDir() {
			// Create folder
			if err := os.MkdirAll(fpath, os.ModePerm); err != nil {
				return err
			}
			continue
		}

		// Make sure target folder exists
		if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
			return err
		}

		// Open file in archive
		rc, err := f.Open()
		if err != nil {
			return err
		}

		// Create destination file
		outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			rc.Close()
			return err
		}

		// Copy file contents
		_, err = io.Copy(outFile, rc)

		// Close files
		outFile.Close()
		rc.Close()

		if err != nil {
			return err
		}

	}

	return nil
}

// stopService runs "sc stop <service>"
func stopService(name string) error {
	cmd := exec.Command("sc", "stop", name)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return err
	}
	log.Printf("Stopped service %s: %s", name, output)
	return nil
}

// stopAppPool runs appcmd to stop an IIS app pool
func stopAppPool(name string) error {
	cmd := exec.Command(`C:\Windows\System32\inetsrv\appcmd.exe`, "stop", "apppool", "/apppool.name:"+name)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to stop app pool %s: %v, output: %s", name, err, string(output))
	}
	log.Printf("Stopped app pool %s: %s", name, string(output))
	return nil
}

func cleanupRoot(rootDir string) error {
	// Default cleanup targets
	targets := []string{
		"dist",
		filepath.Join("frontend", "dist"),
		filepath.Join("lstDocs", "build"),
	}

	// Check if .includeCleanup file exists
	cleanupFile := filepath.Join(rootDir, ".includeCleanup")
	if _, err := os.Stat(cleanupFile); err == nil {
		f, err := os.Open(cleanupFile)
		if err != nil {
			return err
		}
		defer f.Close()

		sc := bufio.NewScanner(f)
		for sc.Scan() {
			line := strings.TrimSpace(sc.Text())
			if line == "" || strings.HasPrefix(line, "#") {
				continue
			}
			targets = append(targets, line)
		}
		if sc.Err() != nil {
			return sc.Err()
		}
	}

	// Delete them (remove dirs/files under rootDir)
	for _, t := range targets {
		p := filepath.Join(rootDir, t)
		if _, err := os.Stat(p); err == nil {
			log.Printf("Removing %s", p)
			if err := os.RemoveAll(p); err != nil {
				return err
			}
		}
	}
	return nil
}

func extractVersion(filename string) (int, error) {
	re := regexp.MustCompile(`release-(\d+)\.zip`)
	m := re.FindStringSubmatch(filename)
	if m == nil {
		return 0, fmt.Errorf("filename does not match release pattern: %s", filename)
	}
	return strconv.Atoi(m[1])
}

func runNPMInstall(rootDir string, folder string) error {

	var folderDir string
    if folder == "" || folder == "." {
        folderDir = rootDir
    } else {
        folderDir = filepath.Join(rootDir, folder)
    }
	cmd := exec.Command("npm", "install")
	cmd.Dir = folderDir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	log.Println("Running npm install in", folderDir)
	return cmd.Run()
}

func runNPMMigrate(rootDir string, folder string) error {

	var folderDir string
    if folder == "" || folder == "." {
        folderDir = rootDir
    } else {
        folderDir = filepath.Join(rootDir, folder)
    }
	
	cmd := exec.Command("npm", "run", "db:migrate")
	cmd.Dir = folderDir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	log.Println("Running npm run db:migrate in", folderDir)
	return cmd.Run()
}

func startService(name string) error {
	cmd := exec.Command("sc", "start", name)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return err
	}
	log.Printf("Started service %s: %s", name, output)
	return nil
}

func startAppPool(name string) error {
	cmd := exec.Command(`C:\Windows\System32\inetsrv\appcmd.exe`, "start", "apppool", "/apppool.name:"+name)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to stop app pool %s: %v, output: %s", name, err, string(output))
	}
	log.Printf("Stopped app pool %s: %s", name, string(output))
	return nil
}
