package report

import (
	"encoding/csv"
	"os"
	"path/filepath"
	"strconv"
)

// Stage identifies which half of a combined row a run fills in: a deploy run
// writes the deploy columns, a submit run the submit columns. Both land in
// the same CSV, so the file always holds one row per export folder.
type Stage int

const (
	StageDeploy Stage = iota
	StageSubmit
)

// FolderRow is one export folder's combined deploy + submit outcome.
type FolderRow struct {
	Bucket       string
	Foldering    string
	Name         string
	Source       string
	Files        int
	URL          string
	Create       string
	Public       string
	Upload       string
	DeployStatus string
	DeployError  string
	Duration     string
	SubmitStatus string
	URLCount     int
	SubmitNotes  string
}

var csvHeader = []string{
	"bucket", "foldering", "name", "source", "files", "url", "create",
	"public", "upload", "deploy_status", "deploy_error", "duration",
	"submit_status", "url_count", "submit_notes",
}

// MergeCSV upserts rows into the combined deploy/submit report keyed by
// foldering: only the columns of the given stage are replaced, so a submit
// run after a deploy run fills in the submit columns of the same rows.
func MergeCSV(path string, stage Stage, rows []FolderRow) error {
	if len(rows) == 0 {
		return nil
	}
	byKey := map[string]FolderRow{}
	var order []string
	if cur, err := readCSV(path); err == nil {
		for _, r := range cur {
			byKey[r.Foldering] = r
			order = append(order, r.Foldering)
		}
	}
	for _, r := range rows {
		old := byKey[r.Foldering]
		merged := merge(old, r, stage)
		if _, seen := byKey[r.Foldering]; !seen {
			order = append(order, r.Foldering)
		}
		byKey[r.Foldering] = merged
	}
	return writeCSV(path, byKey, order)
}

// merge replaces only the columns belonging to stage.
func merge(old, cur FolderRow, stage Stage) FolderRow {
	out := old
	out.Bucket = firstNonEmpty(cur.Bucket, old.Bucket)
	out.Foldering = firstNonEmpty(cur.Foldering, old.Foldering)
	out.Name = firstNonEmpty(cur.Name, old.Name)
	out.Source = firstNonEmpty(cur.Source, old.Source)
	if stage == StageDeploy {
		out.Files = cur.Files
		out.URL = cur.URL
		out.Create = cur.Create
		out.Public = cur.Public
		out.Upload = cur.Upload
		out.DeployStatus = cur.DeployStatus
		out.DeployError = cur.DeployError
		out.Duration = cur.Duration
		return out
	}
	out.SubmitStatus = cur.SubmitStatus
	out.URLCount = cur.URLCount
	out.SubmitNotes = cur.SubmitNotes
	return out
}

func firstNonEmpty(vals ...string) string {
	for _, v := range vals {
		if v != "" {
			return v
		}
	}
	return ""
}

func readCSV(path string) ([]FolderRow, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	records, err := csv.NewReader(f).ReadAll()
	if err != nil {
		return nil, err
	}
	var out []FolderRow
	for i, rec := range records {
		if i == 0 && len(rec) > 0 && rec[0] == "bucket" {
			continue
		}
		var r FolderRow
		get := func(idx int) string {
			if idx < len(rec) {
				return rec[idx]
			}
			return ""
		}
		r.Bucket = get(0)
		r.Foldering = get(1)
		r.Name = get(2)
		r.Source = get(3)
		r.Files, _ = strconv.Atoi(get(4))
		r.URL = get(5)
		r.Create = get(6)
		r.Public = get(7)
		r.Upload = get(8)
		r.DeployStatus = get(9)
		r.DeployError = get(10)
		r.Duration = get(11)
		r.SubmitStatus = get(12)
		r.URLCount, _ = strconv.Atoi(get(13))
		r.SubmitNotes = get(14)
		out = append(out, r)
	}
	return out, nil
}

func writeCSV(path string, byKey map[string]FolderRow, order []string) error {
	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
		return err
	}
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()
	w := csv.NewWriter(f)
	if err := w.Write(csvHeader); err != nil {
		return err
	}
	for _, key := range order {
		r := byKey[key]
		rec := []string{
			r.Bucket, r.Foldering, r.Name, r.Source,
			strconv.Itoa(r.Files), r.URL, r.Create, r.Public, r.Upload,
			r.DeployStatus, r.DeployError, r.Duration,
			r.SubmitStatus, strconv.Itoa(r.URLCount), r.SubmitNotes,
		}
		if err := w.Write(rec); err != nil {
			return err
		}
	}
	w.Flush()
	return w.Error()
}
