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

package repo

import (
	"net/http"
	"time"

	updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)

// GetLicenseSettings returns the licensing/update stream settings for a repo.
func GetLicenseSettings(ctx *context.APIContext) {
	cfg := updateserver_model.GetEffectiveConfig(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
	if cfg == nil {
		ctx.JSON(http.StatusOK, &structs.LicenseSettings{})
		return
	}
	ctx.JSON(http.StatusOK, &structs.LicenseSettings{
		LicensingEnabled: cfg.LicensingEnabled,
		RequireKey:       cfg.RequireKey,
		DownloadGating:   cfg.DownloadGating,
		Platform:         cfg.Platform,
		SupportURL:       cfg.SupportURL,
		ExtensionName:    cfg.ExtensionName,
		DisplayName:      cfg.DisplayName,
		ExtensionType:    cfg.ExtensionType,
		Maintainer:       cfg.Maintainer,
		MaintainerURL:    cfg.MaintainerURL,
		InfoURL:          cfg.InfoURL,
		TargetVersion:    cfg.TargetVersion,
		PHPMinimum:       cfg.PHPMinimum,
	})
}

// UpdateLicenseSettings saves licensing/update stream settings for a repo.
func UpdateLicenseSettings(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*structs.LicenseSettings)

	cfg := &updateserver_model.UpdateStreamConfig{
		OwnerID:          ctx.Repo.Repository.OwnerID,
		RepoID:           ctx.Repo.Repository.ID,
		LicensingEnabled: form.LicensingEnabled,
		RequireKey:       form.RequireKey,
		DownloadGating:   form.DownloadGating,
		Platform:         form.Platform,
		SupportURL:       form.SupportURL,
		ExtensionName:    form.ExtensionName,
		DisplayName:      form.DisplayName,
		ExtensionType:    form.ExtensionType,
		Maintainer:       form.Maintainer,
		MaintainerURL:    form.MaintainerURL,
		InfoURL:          form.InfoURL,
		TargetVersion:    form.TargetVersion,
		PHPMinimum:       form.PHPMinimum,
		StreamMode:       "joomla",
	}

	if err := updateserver_model.SaveConfig(ctx, cfg); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusOK, form)
}

// verifyPackageOwnership checks that a package belongs to the current repo's owner.
func verifyPackageOwnership(ctx *context.APIContext, pkg *updateserver_model.LicensePackage) bool {
	if pkg.OwnerID != ctx.Repo.Repository.OwnerID {
		ctx.APIErrorNotFound(nil)
		return false
	}
	return true
}

// verifyKeyOwnership checks that a key belongs to the current repo's owner.
func verifyKeyOwnership(ctx *context.APIContext, key *updateserver_model.LicenseKey) bool {
	if key.OwnerID != ctx.Repo.Repository.OwnerID {
		ctx.APIErrorNotFound(nil)
		return false
	}
	return true
}

func toLicensePackageAPI(pkg *updateserver_model.LicensePackage) *structs.LicensePackage {
	return &structs.LicensePackage{
		ID:              pkg.ID,
		OwnerID:         pkg.OwnerID,
		Name:            pkg.Name,
		Description:     pkg.Description,
		DurationDays:    pkg.DurationDays,
		MaxSites:        pkg.MaxSites,
		RepoScope:       pkg.RepoScope,
		AllowedChannels: pkg.AllowedChannels,
		IsActive:        pkg.IsActive,
		Created:         time.Unix(int64(pkg.CreatedUnix), 0),
		Updated:         time.Unix(int64(pkg.UpdatedUnix), 0),
	}
}

func toLicenseKeyAPI(key *updateserver_model.LicenseKey) *structs.LicenseKey {
	lk := &structs.LicenseKey{
		ID:                key.ID,
		PackageID:         key.PackageID,
		OwnerID:           key.OwnerID,
		KeyPrefix:         key.KeyPrefix,
		LicenseeName:      key.LicenseeName,
		LicenseeEmail:     key.LicenseeEmail,
		DomainRestriction: key.DomainRestriction,
		MaxSites:          key.MaxSites,
		IsInternal:        key.IsInternal,
		IsActive:          key.IsActive,
		Created:           time.Unix(int64(key.CreatedUnix), 0),
	}
	if key.StartsUnix > 0 {
		t := time.Unix(int64(key.StartsUnix), 0)
		lk.StartsAt = &t
	}
	if key.ExpiresUnix > 0 {
		t := time.Unix(int64(key.ExpiresUnix), 0)
		lk.ExpiresAt = &t
	}
	if key.LastHeartbeatUnix > 0 {
		t := time.Unix(int64(key.LastHeartbeatUnix), 0)
		lk.LastHeartbeat = &t
	}
	return lk
}

// ListLicensePackages lists license packages for the repo owner.
func ListLicensePackages(ctx *context.APIContext) {
	pkgs, err := updateserver_model.ListLicensePackages(ctx, ctx.Repo.Repository.OwnerID)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	result := make([]*structs.LicensePackage, len(pkgs))
	for i, pkg := range pkgs {
		result[i] = toLicensePackageAPI(pkg)
	}
	ctx.JSON(http.StatusOK, result)
}

// CreateLicensePackage creates a new license package.
func CreateLicensePackage(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*structs.CreateLicensePackageOption)

	pkg := &updateserver_model.LicensePackage{
		OwnerID:         ctx.Repo.Repository.OwnerID,
		Name:            form.Name,
		Description:     form.Description,
		DurationDays:    form.DurationDays,
		MaxSites:        form.MaxSites,
		RepoScope:       form.RepoScope,
		AllowedChannels: form.AllowedChannels,
		IsActive:        true,
	}
	if pkg.RepoScope == "" {
		pkg.RepoScope = "all"
	}

	if err := updateserver_model.CreateLicensePackage(ctx, pkg); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusCreated, toLicensePackageAPI(pkg))
}

// EditLicensePackage edits a license package via API.
func EditLicensePackage(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*structs.EditLicensePackageOption)
	pkgID := ctx.PathParamInt64("id")

	pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
	if err != nil {
		ctx.APIErrorNotFound(err)
		return
	}
	if !verifyPackageOwnership(ctx, pkg) {
		return
	}

	if pkg.Name == updateserver_model.MasterPackageName {
		ctx.APIError(http.StatusForbidden, "master package cannot be edited")
		return
	}

	if form.Name != nil {
		pkg.Name = *form.Name
	}
	if form.Description != nil {
		pkg.Description = *form.Description
	}
	if form.DurationDays != nil {
		pkg.DurationDays = *form.DurationDays
	}
	if form.MaxSites != nil {
		pkg.MaxSites = *form.MaxSites
	}
	if form.RepoScope != nil {
		pkg.RepoScope = *form.RepoScope
	}
	if form.AllowedChannels != nil {
		pkg.AllowedChannels = *form.AllowedChannels
	}
	if form.IsActive != nil {
		pkg.IsActive = *form.IsActive
	}

	if err := updateserver_model.UpdateLicensePackage(ctx, pkg); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusOK, toLicensePackageAPI(pkg))
}

// DeleteLicensePackage deletes a license package via API.
func DeleteLicensePackage(ctx *context.APIContext) {
	pkgID := ctx.PathParamInt64("id")

	pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
	if err != nil {
		ctx.APIErrorNotFound(err)
		return
	}
	if !verifyPackageOwnership(ctx, pkg) {
		return
	}

	if pkg.Name == updateserver_model.MasterPackageName {
		ctx.APIError(http.StatusForbidden, "master package cannot be deleted")
		return
	}

	if err := updateserver_model.DeleteLicensePackage(ctx, pkgID); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.Status(http.StatusNoContent)
}

// ArchiveLicensePackage archives a license package via API.
func ArchiveLicensePackage(ctx *context.APIContext) {
	pkgID := ctx.PathParamInt64("id")

	pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
	if err != nil {
		ctx.APIErrorNotFound(err)
		return
	}
	if !verifyPackageOwnership(ctx, pkg) {
		return
	}

	if pkg.Name == updateserver_model.MasterPackageName {
		ctx.APIError(http.StatusForbidden, "master package cannot be archived")
		return
	}

	if err := updateserver_model.ArchiveLicensePackage(ctx, pkgID); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.Status(http.StatusNoContent)
}

// UnarchiveLicensePackage restores an archived license package via API.
func UnarchiveLicensePackage(ctx *context.APIContext) {
	pkgID := ctx.PathParamInt64("id")
	pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
	if err != nil {
		ctx.APIErrorNotFound(err)
		return
	}
	if !verifyPackageOwnership(ctx, pkg) {
		return
	}
	if err := updateserver_model.UnarchiveLicensePackage(ctx, pkgID); err != nil {
		ctx.APIErrorInternal(err)
		return
	}
	ctx.Status(http.StatusNoContent)
}

// ListLicenseKeys lists license keys for the repo owner.
func ListLicenseKeys(ctx *context.APIContext) {
	keys, err := updateserver_model.ListLicenseKeys(ctx, ctx.Repo.Repository.OwnerID)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	result := make([]*structs.LicenseKey, len(keys))
	for i, key := range keys {
		result[i] = toLicenseKeyAPI(key)
	}
	ctx.JSON(http.StatusOK, result)
}

// CreateLicenseKey creates a new license key.
func CreateLicenseKey(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*structs.CreateLicenseKeyOption)

	key := &updateserver_model.LicenseKey{
		PackageID:         form.PackageID,
		OwnerID:           ctx.Repo.Repository.OwnerID,
		LicenseeName:      form.LicenseeName,
		LicenseeEmail:     form.LicenseeEmail,
		DomainRestriction: form.DomainRestriction,
		MaxSites:          form.MaxSites,
		IsActive:          true,
	}

	if form.StartsAt != nil {
		key.StartsUnix = timeutil.TimeStamp(form.StartsAt.Unix())
	}

	if form.ExpiresAt != nil {
		key.ExpiresUnix = timeutil.TimeStamp(form.ExpiresAt.Unix())
	} else {
		// Auto-calculate from package duration.
		pkg, err := updateserver_model.GetLicensePackageByID(ctx, form.PackageID)
		if err != nil {
			ctx.APIErrorInternal(err)
			return
		}
		if pkg.DurationDays > 0 {
			start := time.Now()
			if form.StartsAt != nil {
				start = *form.StartsAt
			}
			expires := start.AddDate(0, 0, pkg.DurationDays)
			key.ExpiresUnix = timeutil.TimeStamp(expires.Unix())
		}
	}

	rawKey, err := updateserver_model.CreateLicenseKey(ctx, key)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	resp := &structs.LicenseKeyCreated{
		LicenseKey: *toLicenseKeyAPI(key),
		RawKey:     rawKey,
	}
	ctx.JSON(http.StatusCreated, resp)
}

// EditLicenseKey edits a license key via API.
func EditLicenseKey(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*structs.EditLicenseKeyOption)
	keyID := ctx.PathParamInt64("id")

	key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
	if err != nil {
		ctx.APIErrorNotFound(err)
		return
	}
	if !verifyKeyOwnership(ctx, key) {
		return
	}

	if key.IsInternal {
		ctx.APIError(http.StatusForbidden, "master keys cannot be edited")
		return
	}

	if form.LicenseeName != nil {
		key.LicenseeName = *form.LicenseeName
	}
	if form.LicenseeEmail != nil {
		key.LicenseeEmail = *form.LicenseeEmail
	}
	if form.DomainRestriction != nil {
		key.DomainRestriction = *form.DomainRestriction
	}
	if form.MaxSites != nil {
		key.MaxSites = *form.MaxSites
	}
	if form.IsActive != nil {
		key.IsActive = *form.IsActive
	}
	if form.ExpiresAt != nil {
		key.ExpiresUnix = timeutil.TimeStamp(form.ExpiresAt.Unix())
	}

	if err := updateserver_model.UpdateLicenseKey(ctx, key); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusOK, toLicenseKeyAPI(key))
}

// PurchaseLicenseKey handles purchase webhook — creates a key from a payment event.
func PurchaseLicenseKey(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*structs.PurchaseLicenseKeyOption)

	// Idempotency check: if payment_ref already exists, return existing key.
	if form.PaymentRef != "" {
		existing, err := updateserver_model.GetLicenseKeyByPaymentRef(ctx, form.PaymentRef)
		if err == nil {
			resp := &structs.LicenseKeyCreated{
				LicenseKey: *toLicenseKeyAPI(existing),
				RawKey:     "", // raw key not available after creation
			}
			ctx.JSON(http.StatusOK, resp)
			return
		}
	}

	pkg, err := updateserver_model.GetLicensePackageByID(ctx, form.PackageID)
	if err != nil {
		ctx.APIErrorNotFound(err)
		return
	}
	if !verifyPackageOwnership(ctx, pkg) {
		return
	}

	key := &updateserver_model.LicenseKey{
		PackageID:         form.PackageID,
		OwnerID:           ctx.Repo.Repository.OwnerID,
		LicenseeName:      form.LicenseeName,
		LicenseeEmail:     form.LicenseeEmail,
		DomainRestriction: form.Domain,
		PaymentRef:        form.PaymentRef,
		IsActive:          true,
	}

	if pkg.DurationDays > 0 {
		expires := time.Now().AddDate(0, 0, pkg.DurationDays)
		key.ExpiresUnix = timeutil.TimeStamp(expires.Unix())
	}

	rawKey, err := updateserver_model.CreateLicenseKey(ctx, key)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	resp := &structs.LicenseKeyCreated{
		LicenseKey: *toLicenseKeyAPI(key),
		RawKey:     rawKey,
	}
	ctx.JSON(http.StatusCreated, resp)
}

// RenewLicenseKey extends a key's expiration by its package duration.
func RenewLicenseKey(ctx *context.APIContext) {
	keyID := ctx.PathParamInt64("id")
	key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
	if err != nil {
		ctx.APIErrorNotFound(err)
		return
	}
	if !verifyKeyOwnership(ctx, key) {
		return
	}

	pkg, err := updateserver_model.GetLicensePackageByID(ctx, key.PackageID)
	if err != nil {
		ctx.APIErrorNotFound(err)
		return
	}

	days := pkg.DurationDays
	if days == 0 {
		days = 365
	}

	if err := updateserver_model.RenewLicenseKey(ctx, keyID, days); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	// Reload key to get updated fields.
	key, _ = updateserver_model.GetLicenseKeyByID(ctx, keyID)
	ctx.JSON(http.StatusOK, toLicenseKeyAPI(key))
}

// RevokeLicenseKey deactivates a license key via API.
func RevokeLicenseKey(ctx *context.APIContext) {
	keyID := ctx.PathParamInt64("id")
	key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
	if err != nil {
		ctx.APIErrorNotFound(err)
		return
	}
	if !verifyKeyOwnership(ctx, key) {
		return
	}

	key.IsActive = false
	if err := updateserver_model.UpdateLicenseKey(ctx, key); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusOK, toLicenseKeyAPI(key))
}

// DeleteLicenseKey deletes a license key.
func DeleteLicenseKey(ctx *context.APIContext) {
	keyID := ctx.PathParamInt64("id")
	key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
	if err != nil {
		ctx.APIErrorNotFound(err)
		return
	}
	if !verifyKeyOwnership(ctx, key) {
		return
	}
	if err := updateserver_model.DeleteLicenseKey(ctx, keyID); err != nil {
		ctx.APIErrorInternal(err)
		return
	}
	ctx.Status(http.StatusNoContent)
}

// ValidateLicenseKey validates a license key — public endpoint (no auth required).
func ValidateLicenseKey(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*structs.ValidateLicenseKeyOption)

	key, pkg, err := updateserver_model.ValidateLicenseKey(ctx, form.Key, form.Domain)
	if err != nil {
		ctx.JSON(http.StatusOK, &structs.ValidateLicenseKeyResponse{
			Valid:   false,
			Message: err.Error(),
		})
		return
	}

	_ = updateserver_model.TouchHeartbeat(ctx, key.ID)

	var expiresAt *time.Time
	if key.ExpiresUnix > 0 {
		t := time.Unix(int64(key.ExpiresUnix), 0)
		expiresAt = &t
	}

	maxSites := key.MaxSites
	if maxSites == 0 {
		maxSites = pkg.MaxSites
	}

	sitesUsed, _ := updateserver_model.CountUniqueDomainsByKey(ctx, key.ID)

	ctx.JSON(http.StatusOK, &structs.ValidateLicenseKeyResponse{
		Valid:       true,
		PackageName: pkg.Name,
		Channels:    pkg.AllowedChannels,
		ExpiresAt:   expiresAt,
		SitesUsed:   sitesUsed,
		MaxSites:    maxSites,
	})
}

// GetLicenseKeyUsage returns usage logs for a license key.
func GetLicenseKeyUsage(ctx *context.APIContext) {
	usages, err := updateserver_model.GetRecentUsage(ctx, ctx.PathParamInt64("id"), 100)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	result := make([]*structs.LicenseKeyUsage, len(usages))
	for i, u := range usages {
		result[i] = &structs.LicenseKeyUsage{
			ID:          u.ID,
			KeyID:       u.KeyID,
			RepoID:      u.RepoID,
			Domain:      u.Domain,
			IPAddress:   u.IPAddress,
			UserAgent:   u.UserAgent,
			VersionFrom: u.VersionFrom,
			Created:     time.Unix(int64(u.CreatedUnix), 0),
		}
	}
	ctx.JSON(http.StatusOK, result)
}
