package main

import (
	"github.com/c2h5oh/datasize"
	"github.com/go-chi/render"
	log "github.com/sirupsen/logrus"
	"net/http"
	"os"
	"somegit.dev/ALHP/ALHP.GO/ent"
	"somegit.dev/ALHP/ALHP.GO/ent/dbpackage"
	"somegit.dev/ALHP/ALHP.GO/ent/predicate"
	"strconv"
	"strings"
	"time"
)

type StatsResponse struct {
	Failed              int    `json:"failed"`
	Skipped             int    `json:"skipped"`
	Latest              int    `json:"latest"`
	Queued              int    `json:"queued"`
	Building            int    `json:"building"`
	LastMirrorTimestamp *int64 `json:"last_mirror_timestamp,omitempty"`
	LTO                 *struct {
		Enabled  int `json:"enabled"`
		Disabled int `json:"disabled"`
		Unknown  int `json:"unknown"`
	} `json:"lto"`
}

type ThinPackage struct {
	Pkgbase       string                 `json:"pkgbase"`
	Repo          string                 `json:"repo"`
	SplitPackages []string               `json:"split_packages"`
	Status        dbpackage.Status       `json:"status"`
	SkipReason    *string                `json:"skip_reason,omitempty"`
	LTO           dbpackage.Lto          `json:"lto"`
	DebugSymbols  dbpackage.DebugSymbols `json:"debug_symbols"`
	ArchVersion   string                 `json:"arch_version"`
	RepoVersion   string                 `json:"repo_version"`
	BuildDate     *string                `json:"build_date,omitempty"`
	PeakMem       *string                `json:"peak_mem,omitempty"`
}

type PackageResponse struct {
	Packages []*ThinPackage `json:"packages"`
	Total    int            `json:"total"`
	Offset   int            `json:"offset"`
	Limit    int            `json:"limit"`
}

func GetStats(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	var v []struct {
		Status dbpackage.Status `json:"status"`
		Count  int              `json:"count"`
	}

	db.DBPackage.Query().GroupBy(dbpackage.FieldStatus).Aggregate(ent.Count()).ScanX(ctx, &v)

	resp := new(StatsResponse)
	resp.LTO = new(struct {
		Enabled  int `json:"enabled"`
		Disabled int `json:"disabled"`
		Unknown  int `json:"unknown"`
	})
	for _, c := range v {
		switch c.Status {
		case dbpackage.StatusFailed:
			resp.Failed = c.Count
		case dbpackage.StatusSkipped:
			resp.Skipped = c.Count
		case dbpackage.StatusLatest:
			resp.Latest = c.Count
		case dbpackage.StatusQueued:
			resp.Queued = c.Count
		case dbpackage.StatusBuilding:
			resp.Building = c.Count
		}
	}

	var v2 []struct {
		Status dbpackage.Lto `json:"lto"`
		Count  int           `json:"count"`
	}

	db.DBPackage.Query().Where(dbpackage.StatusNEQ(dbpackage.StatusSkipped)).
		GroupBy(dbpackage.FieldLto).Aggregate(ent.Count()).ScanX(ctx, &v2)

	for _, c := range v2 {
		switch c.Status {
		case dbpackage.LtoUnknown:
			resp.LTO.Unknown = c.Count
		case dbpackage.LtoDisabled, dbpackage.LtoAutoDisabled:
			resp.LTO.Disabled += c.Count
		case dbpackage.LtoEnabled:
			resp.LTO.Enabled = c.Count
		}
	}

	if os.Getenv("ALHP_TIMESTAMP_PATH") != "" {
		tsFile, err := os.ReadFile(os.Getenv("ALHP_TIMESTAMP_PATH"))
		if err != nil {
			log.Warningf("error reading timestamp file: %v", err)
		} else {
			ts, err := strconv.ParseInt(string(tsFile), 10, 64)
			if err != nil {
				log.Warningf("error parsing timestamp file: %v", err)
			} else {
				resp.LastMirrorTimestamp = &ts
			}
		}
	}

	render.Status(r, http.StatusOK)
	render.JSON(w, r, resp)
}

func GetPackages(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	pkgbase := r.URL.Query().Get("pkgbase")
	status := r.URL.Query()["status"]
	repo := r.URL.Query().Get("repo")

	offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
	if err != nil {
		log.Debugf("error parsing page: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
	if err != nil {
		log.Debugf("error parsing page: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	var constraints []predicate.DBPackage

	if pkgbase != "" {
		if r.URL.Query().Has("exact") {
			constraints = append(constraints, dbpackage.PkgbaseEQ(pkgbase))
		} else {
			constraints = append(constraints, dbpackage.PkgbaseContains(pkgbase))
		}
	}

	if len(status) > 0 {
		constraints = append(constraints, dbpackage.StatusIn(func(status []string) (result []dbpackage.Status) {
			for _, s := range status {
				result = append(result, dbpackage.Status(s))
			}
			return result
		}(status)...))
	}

	if repo != "" {
		before, after, found := strings.Cut(repo, "-")
		if found {
			constraints = append(constraints, dbpackage.And(dbpackage.MarchEQ(after), dbpackage.RepositoryEQ(dbpackage.Repository(before))))
		}
	}

	nPkgs, err := db.DBPackage.Query().Where(constraints...).Count(ctx)
	if err != nil {
		log.Warningf("error getting package count from db: %v", err)
		render.Status(r, http.StatusInternalServerError)
		return
	}

	if nPkgs == 0 {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	rPackages, err := db.DBPackage.Query().Where(constraints...).Limit(limit).Offset(offset).All(ctx)
	if err != nil {
		log.Warningf("error getting packages from db: %v", err)
		render.Status(r, http.StatusInternalServerError)
		return
	}

	resp := new(PackageResponse)
	for _, mPkg := range rPackages {
		nPkg := &ThinPackage{
			Pkgbase:       mPkg.Pkgbase,
			Repo:          mPkg.Repository.String() + "-" + mPkg.March,
			SplitPackages: mPkg.Packages,
			Status:        mPkg.Status,
			LTO:           mPkg.Lto,
			DebugSymbols:  mPkg.DebugSymbols,
			ArchVersion:   mPkg.Version,
			RepoVersion:   mPkg.RepoVersion,
		}

		if mPkg.SkipReason != "" {
			nPkg.SkipReason = &mPkg.SkipReason
		}

		if !mPkg.BuildTimeStart.IsZero() {
			formattedBuildDate := mPkg.BuildTimeStart.UTC().Format(time.RFC1123)
			nPkg.BuildDate = &formattedBuildDate
		}

		if mPkg.MaxRss != nil {
			hrSize := (datasize.ByteSize(*mPkg.MaxRss) * datasize.KB).HumanReadable()
			nPkg.PeakMem = &hrSize
		}

		resp.Packages = append(resp.Packages, nPkg)
	}
	resp.Offset = offset
	resp.Limit = limit
	resp.Total = nPkgs

	render.Status(r, http.StatusOK)
	render.JSON(w, r, resp)
}
