package deploy

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"sync"
	"time"

	"pinscrape-allgo/internal/config"
	"pinscrape-allgo/internal/csvgen"
)

type Result struct {
	Bucket    string `json:"bucket"`
	Foldering string `json:"foldering"`
	Name      string `json:"name"`
	Source    string `json:"source"`
	Files     int    `json:"files"`
	URL       string `json:"url"`
	Create    string `json:"create"`
	Public    string `json:"public"`
	Upload    string `json:"upload"`
	Status    string `json:"status"`
	Error     string `json:"error"`
	Duration  string `json:"duration"`
}

func resolveGcloud() string {
	var candidates []string
	if la := os.Getenv("LOCALAPPDATA"); la != "" {
		candidates = append(candidates, filepath.Join(la, "Google", "Cloud SDK", "google-cloud-sdk", "bin", "gcloud.cmd"))
	}
	if pf := os.Getenv("ProgramFiles"); pf != "" {
		candidates = append(candidates, filepath.Join(pf, "Google", "Cloud SDK", "google-cloud-sdk", "bin", "gcloud.cmd"))
	}
	if pf := os.Getenv("ProgramFiles(x86)"); pf != "" {
		candidates = append(candidates, filepath.Join(pf, "Google", "Cloud SDK", "google-cloud-sdk", "bin", "gcloud.cmd"))
	}
	for _, c := range candidates {
		if _, err := os.Stat(c); err == nil {
			return c
		}
	}
	return "gcloud"
}

func runCmd(timeout time.Duration, name string, args ...string) (bool, string) {
	cmd := exec.Command(name, args...)
	if runtime.GOOS == "windows" {
		// gcloud.cmd requires a shell on Windows.
		cmd = exec.Command("cmd", append([]string{"/c", name}, args...)...)
	}
	var out, errb strings.Builder
	cmd.Stdout = &out
	cmd.Stderr = &errb
	if err := cmd.Start(); err != nil {
		return false, err.Error()
	}
	done := make(chan error, 1)
	go func() { done <- cmd.Wait() }()
	select {
	case <-done:
		return cmd.ProcessState.Success(), strings.TrimSpace(errb.String())
	case <-time.After(timeout):
		cmd.Process.Kill()
		return false, "command timed out"
	}
}

func countHTML(folder string) int {
	matches, _ := filepath.Glob(filepath.Join(folder, "*.html"))
	return len(matches)
}

func processMapping(gcloud string, m csvgen.Mapping, location, exportFolder string) Result {
	start := time.Now()
	res := Result{
		Bucket:    m.Bucket,
		Foldering: m.Foldering,
		Name:      m.Name,
		Source:    m.Source,
	}
	folderPath := filepath.Join(exportFolder, m.Foldering)
	if st, err := os.Stat(folderPath); err != nil || !st.IsDir() {
		res.Error = fmt.Sprintf("Folder not found: %s", m.Foldering)
		res.Status = "SKIP"
		res.Duration = fmt.Sprintf("%.1f", time.Since(start).Seconds())
		log.Printf("  [SKIP] %s - folder not found", m.Foldering)
		return res
	}
	res.Files = countHTML(folderPath)

	log.Printf("  [CREATE] gs://%s", m.Bucket)
	ok, errOut := runCmd(300*time.Second, gcloud, "storage", "buckets", "create",
		"gs://"+m.Bucket, "--location="+location, "--uniform-bucket-level-access")
	if !ok {
		if strings.Contains(errOut, "AlreadyExists") || strings.Contains(errOut, "409") {
			log.Printf("  [EXISTS] gs://%s", m.Bucket)
		} else {
			res.Error = "Bucket creation failed: " + errOut
			res.Status = "FAILED"
			res.Duration = fmt.Sprintf("%.1f", time.Since(start).Seconds())
			return res
		}
	}
	res.Create = "YES"

	log.Printf("  [PUBLIC] gs://%s", m.Bucket)
	ok, errOut = runCmd(300*time.Second, gcloud, "storage", "buckets", "add-iam-policy-binding",
		"gs://"+m.Bucket, "--member=allUsers", "--role=roles/storage.objectViewer")
	if !ok {
		res.Error = "Public access failed: " + errOut
		res.Status = "FAILED"
		res.Duration = fmt.Sprintf("%.1f", time.Since(start).Seconds())
		return res
	}
	res.Public = "YES"

	log.Printf("  [UPLOAD] %s -> gs://%s/", m.Foldering, m.Bucket)
	ok, errOut = runCmd(600*time.Second, gcloud, "storage", "cp", "-r",
		filepath.Join(folderPath, "*"), "gs://"+m.Bucket+"/")
	if !ok {
		res.Error = "Upload failed: " + errOut
		res.Status = "FAILED"
		res.Duration = fmt.Sprintf("%.1f", time.Since(start).Seconds())
		return res
	}
	res.Upload = "YES"
	res.Status = "SUCCESS"
	res.URL = fmt.Sprintf("https://storage.googleapis.com/%s/", m.Bucket)
	res.Duration = fmt.Sprintf("%.1f", time.Since(start).Seconds())
	log.Printf("  [SUCCESS] %s -> gs://%s/ (%ss)", m.Foldering, m.Bucket, res.Duration)
	return res
}

// Run creates buckets, sets public access and uploads export folders using the
// mappings from CREATE.csv, with bounded parallelism.
func Run(cfg *config.Config, location string, concurrency int, csvPath string) ([]Result, error) {
	gcloud := resolveGcloud()
	mappings, err := csvgen.ReadMappings(cfg, csvPath)
	if err != nil {
		return nil, err
	}
	if location == "" {
		location = "us-central1"
	}
	if concurrency <= 0 {
		concurrency = 3
	}
	log.Printf("Found %d bucket mappings in %s", len(mappings), csvPath)
	log.Printf("Concurrency: %d, Location: %s", concurrency, location)

	exportFolder := cfg.Path(cfg.ExportFolder)

	var (
		mu      sync.Mutex
		results []Result
		wg      sync.WaitGroup
	)
	sem := make(chan struct{}, concurrency)
	for _, m := range mappings {
		wg.Add(1)
		sem <- struct{}{}
		go func(m csvgen.Mapping) {
			defer wg.Done()
			defer func() { <-sem }()
			r := processMapping(gcloud, m, location, exportFolder)
			mu.Lock()
			results = append(results, r)
			mu.Unlock()
		}(m)
	}
	wg.Wait()
	sort.Slice(results, func(i, j int) bool { return results[i].Foldering < results[j].Foldering })

	success, skip, fail := 0, 0, 0
	for _, r := range results {
		switch r.Status {
		case "SUCCESS":
			success++
		case "SKIP":
			skip++
		default:
			fail++
		}
	}
	log.Println("==================================================")
	log.Println("  DEPLOYMENT SUMMARY")
	log.Printf("Total:   %d", len(results))
	log.Printf("Success: %d", success)
	log.Printf("Skipped: %d", skip)
	log.Printf("Failed:  %d", fail)

	return results, nil
}
