// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later

package licensing

import (
	"fmt"
	"io"
	"net/http"

	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
	licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing"
	repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/storage"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
	licensing_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/licensing"
)

// ServeDownload handles GET /api/v1/licensing/download/{product}/{version}.zip?token=XXX&expires=YYY&dlid=ZZZ
func ServeDownload(ctx *context.APIContext) {
	product := ctx.PathParam("product")
	versionFile := ctx.PathParam("version")
	token := ctx.FormString("token")
	expiresStr := ctx.FormString("expires")
	dlid := ctx.FormString("dlid")

	version, ok := licensing_service.ParseDownloadParams(versionFile)
	if !ok {
		ctx.APIError(http.StatusBadRequest, "invalid version format")
		return
	}

	expires, ok := licensing_service.ParseExpires(expiresStr)
	if !ok || token == "" || dlid == "" {
		ctx.APIError(http.StatusForbidden, "missing or invalid download parameters")
		return
	}

	// Verify signed token
	if !licensing_service.VerifyDownloadToken(token, product, version, dlid, expires) {
		ctx.APIError(http.StatusForbidden, "invalid or expired download token")
		return
	}

	// Verify DLID is still valid
	license, err := licensing_model.GetLicenseByDLID(ctx, dlid)
	if err != nil || license == nil || !license.IsActive() {
		ctx.APIError(http.StatusForbidden, "license invalid or expired")
		return
	}

	// Verify entitlement
	has, _ := licensing_model.HasEntitlement(ctx, license.ID, product)
	if !has {
		ctx.APIError(http.StatusForbidden, "no entitlement for product")
		return
	}

	// Resolve repo from entitlement
	ents, err := licensing_model.GetEntitlementsByLicense(ctx, license.ID)
	if err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to get entitlements")
		return
	}
	var repoOwner, repoName string
	for _, ent := range ents {
		if ent.ProductCode == product {
			repoOwner = ent.RepoOwner
			repoName = ent.RepoName
			break
		}
	}
	if repoName == "" {
		ctx.APIError(http.StatusNotFound, "product repo not found")
		return
	}

	// Find repo
	repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, repoOwner, repoName)
	if err != nil || repo == nil {
		ctx.APIError(http.StatusNotFound, "repository not found")
		return
	}

	// Find the release with matching version
	releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
		RepoID:        repo.ID,
		ListOptions:   db.ListOptionsAll,
		IncludeDrafts: false,
		IncludeTags:   false,
	})
	if err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to list releases")
		return
	}

	var targetRelease *repo_model.Release
	for _, rel := range releases {
		relVersion := extractVersion(rel.TagName)
		if relVersion == version {
			targetRelease = rel
			break
		}
		if rel.Title != "" && extractVersion(rel.Title) == version {
			targetRelease = rel
			break
		}
	}
	if targetRelease == nil {
		ctx.APIError(http.StatusNotFound, fmt.Sprintf("release version %s not found", version))
		return
	}

	// Find ZIP attachment
	var attachments []*repo_model.Attachment
	err = db.GetEngine(ctx).Where("release_id = ?", targetRelease.ID).Find(&attachments)
	if err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to get attachments")
		return
	}

	var zipAttachment *repo_model.Attachment
	for _, att := range attachments {
		if att.Name != "" && len(att.Name) > 4 && att.Name[len(att.Name)-4:] == ".zip" {
			zipAttachment = att
			break
		}
	}
	if zipAttachment == nil {
		ctx.APIError(http.StatusNotFound, "no zip attachment found for release")
		return
	}

	// Log the download
	licensing_model.LogLicenseAudit(ctx, license.ID, "download",
		product, fmt.Sprintf("%s/%s", version, zipAttachment.Name))

	// Serve the file
	fr, err := storage.Attachments.Open(zipAttachment.RelativePath())
	if err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to open attachment")
		return
	}
	defer fr.Close()

	ctx.Resp.Header().Set("Content-Type", "application/zip")
	ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", zipAttachment.Name))
	ctx.Resp.WriteHeader(http.StatusOK)

	if _, err := io.Copy(ctx.Resp, fr); err != nil {
		log.Error("ServeDownload: io.Copy: %v", err)
	}
}
