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

package licensing

import (
	"encoding/json"
	"net/http"
	"strconv"
	"time"

	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
	licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing"
	mojo_json "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)

// ── Admin: License CRUD ─────────────────────────────────────────────────

type createLicenseRequest struct {
	UserID      int64  `json:"user_id" binding:"Required"`
	Tier        string `json:"tier" binding:"Required"`
	MaxDomains  int    `json:"max_domains"`
	ExpiresMonths int  `json:"expires_months"`
	Notes       string `json:"notes"`
}

// CreateLicense handles POST /api/v1/licensing/licenses
func CreateLicense(ctx *context.APIContext) {
	var req createLicenseRequest
	if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid request body")
		return
	}

	// Resolve max_domains from tier if not specified
	maxDomains := req.MaxDomains
	if maxDomains == 0 {
		tier, _ := licensing_model.GetProductTierByKey(ctx, req.Tier)
		if tier != nil {
			maxDomains = tier.MaxDomains
		}
		if maxDomains == 0 {
			maxDomains = 1
		}
	}

	var expiresAt timeutil.TimeStamp
	if req.ExpiresMonths > 0 {
		expiresAt = timeutil.TimeStamp(time.Now().AddDate(0, req.ExpiresMonths, 0).Unix())
	}

	license, err := licensing_model.CreateLicense(ctx, req.UserID, req.Tier, maxDomains, expiresAt)
	if err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to create license")
		return
	}

	if req.Notes != "" {
		license.Notes = req.Notes
		// TODO: update notes field
	}

	// Build entitlements from tier
	if err := licensing_model.RebuildEntitlements(ctx, license.ID, req.Tier); err != nil {
		log.Error("CreateLicense: RebuildEntitlements: %v", err)
	}

	ctx.JSON(http.StatusCreated, licenseToJSON(ctx, license))
}

// ListLicenses handles GET /api/v1/licensing/licenses
func ListLicenses(ctx *context.APIContext) {
	page := ctx.FormInt("page")
	if page <= 0 {
		page = 1
	}
	limit := ctx.FormInt("limit")
	if limit <= 0 || limit > 50 {
		limit = 20
	}

	// For now, get all licenses (pagination via offset)
	// TODO: add proper pagination to the model layer
	var licenses []*licensing_model.License
	err := db.GetEngine(ctx).Limit(limit, (page-1)*limit).Find(&licenses)
	if err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to list licenses")
		return
	}

	results := make([]map[string]any, 0, len(licenses))
	for _, l := range licenses {
		results = append(results, licenseToJSON(ctx, l))
	}
	ctx.JSON(http.StatusOK, results)
}

// GetLicense handles GET /api/v1/licensing/licenses/{id}
func GetLicense(ctx *context.APIContext) {
	id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
	if err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid license ID")
		return
	}

	license, err := licensing_model.GetLicenseByID(ctx, id)
	if err != nil || license == nil {
		ctx.APIErrorNotFound()
		return
	}

	result := licenseToJSON(ctx, license)

	// Include entitlements
	ents, _ := licensing_model.GetEntitlementsByLicense(ctx, license.ID)
	entList := make([]map[string]any, 0, len(ents))
	for _, e := range ents {
		entList = append(entList, map[string]any{
			"product_code": e.ProductCode,
			"repo_owner":   e.RepoOwner,
			"repo_name":    e.RepoName,
			"is_custom":    e.IsCustom,
		})
	}
	result["entitlements"] = entList

	// Include activations
	acts, _ := licensing_model.GetActivationsByLicense(ctx, license.ID)
	actList := make([]map[string]any, 0, len(acts))
	for _, a := range acts {
		actList = append(actList, map[string]any{
			"domain":       a.Domain,
			"ip_address":   a.IPAddress,
			"joomla_ver":   a.JoomlaVer,
			"activated_at": formatTime(a.ActivatedAt),
			"last_seen_at": formatTime(a.LastSeenAt),
		})
	}
	result["activations"] = actList

	ctx.JSON(http.StatusOK, result)
}

type updateLicenseRequest struct {
	Tier       *string `json:"tier"`
	Status     *string `json:"status"`
	MaxDomains *int    `json:"max_domains"`
	ExpiresAt  *string `json:"expires_at"`
	Notes      *string `json:"notes"`
}

// UpdateLicense handles PATCH /api/v1/licensing/licenses/{id}
func UpdateLicense(ctx *context.APIContext) {
	id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
	if err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid license ID")
		return
	}

	license, err := licensing_model.GetLicenseByID(ctx, id)
	if err != nil || license == nil {
		ctx.APIErrorNotFound()
		return
	}

	var req updateLicenseRequest
	if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid request body")
		return
	}

	if req.Tier != nil && *req.Tier != license.Tier {
		if err := licensing_model.UpdateLicenseTier(ctx, id, *req.Tier); err != nil {
			ctx.APIError(http.StatusInternalServerError, "failed to update tier")
			return
		}
		license.Tier = *req.Tier
	}

	if req.Status != nil && *req.Status != license.Status {
		if err := licensing_model.SetLicenseStatus(ctx, id, *req.Status); err != nil {
			ctx.APIError(http.StatusInternalServerError, "failed to update status")
			return
		}
		license.Status = *req.Status
	}

	// Update simple fields directly
	cols := make([]string, 0)
	if req.MaxDomains != nil {
		license.MaxDomains = *req.MaxDomains
		cols = append(cols, "max_domains")
	}
	if req.Notes != nil {
		license.Notes = *req.Notes
		cols = append(cols, "notes")
	}
	if req.ExpiresAt != nil {
		t, err := time.Parse(time.RFC3339, *req.ExpiresAt)
		if err == nil {
			license.ExpiresAt = timeutil.TimeStamp(t.Unix())
			cols = append(cols, "expires_at")
		}
	}
	if len(cols) > 0 {
		cols = append(cols, "updated_at")
		db.GetEngine(ctx).ID(id).Cols(cols...).Update(license)
	}

	ctx.JSON(http.StatusOK, licenseToJSON(ctx, license))
}

// DeleteLicense handles DELETE /api/v1/licensing/licenses/{id}
func DeleteLicense(ctx *context.APIContext) {
	id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
	if err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid license ID")
		return
	}

	if err := licensing_model.RevokeLicense(ctx, id); err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to revoke license")
		return
	}

	ctx.Status(http.StatusNoContent)
}

// ── User: Self-service ──────────────────────────────────────────────────

// MyLicenses handles GET /api/v1/licensing/my/licenses
func MyLicenses(ctx *context.APIContext) {
	licenses, err := licensing_model.GetLicensesByUser(ctx, ctx.Doer.ID)
	if err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to list licenses")
		return
	}

	results := make([]map[string]any, 0, len(licenses))
	for _, l := range licenses {
		results = append(results, licenseToJSON(ctx, l))
	}
	ctx.JSON(http.StatusOK, results)
}

// MyLicenseDomains handles GET /api/v1/licensing/my/licenses/{id}/domains
func MyLicenseDomains(ctx *context.APIContext) {
	id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
	if err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid license ID")
		return
	}

	license, err := licensing_model.GetLicenseByID(ctx, id)
	if err != nil || license == nil || license.UserID != ctx.Doer.ID {
		ctx.APIErrorNotFound()
		return
	}

	acts, err := licensing_model.GetActivationsByLicense(ctx, id)
	if err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to list domains")
		return
	}

	results := make([]map[string]any, 0, len(acts))
	for _, a := range acts {
		results = append(results, map[string]any{
			"domain":       a.Domain,
			"activated_at": formatTime(a.ActivatedAt),
			"last_seen_at": formatTime(a.LastSeenAt),
		})
	}
	ctx.JSON(http.StatusOK, results)
}

// MyDeactivateDomain handles DELETE /api/v1/licensing/my/licenses/{id}/domains/{domain}
func MyDeactivateDomain(ctx *context.APIContext) {
	id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
	if err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid license ID")
		return
	}

	license, err := licensing_model.GetLicenseByID(ctx, id)
	if err != nil || license == nil || license.UserID != ctx.Doer.ID {
		ctx.APIErrorNotFound()
		return
	}

	domain := ctx.PathParam("domain")
	if err := licensing_model.DeactivateDomain(ctx, id, domain); err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to deactivate domain")
		return
	}

	ctx.Status(http.StatusNoContent)
}

// ── Admin: Product Tier CRUD ────────────────────────────────────────────

// ListTiers handles GET /api/v1/licensing/tiers
func ListTiers(ctx *context.APIContext) {
	tiers, err := licensing_model.GetAllProductTiers(ctx)
	if err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to list tiers")
		return
	}

	results := make([]map[string]any, 0, len(tiers))
	for _, t := range tiers {
		results = append(results, tierToJSON(t))
	}
	ctx.JSON(http.StatusOK, results)
}

type createTierRequest struct {
	TierKey    string `json:"tier_key" binding:"Required"`
	TierName   string `json:"tier_name" binding:"Required"`
	Repos      []string `json:"repos"`
	MaxDomains int    `json:"max_domains"`
	SortOrder  int    `json:"sort_order"`
}

// CreateTier handles POST /api/v1/licensing/tiers
func CreateTier(ctx *context.APIContext) {
	var req createTierRequest
	if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid request body")
		return
	}

	reposJSON, _ := mojo_json.Marshal(req.Repos)
	tier := &licensing_model.ProductTier{
		TierKey:    req.TierKey,
		TierName:   req.TierName,
		Repos:      string(reposJSON),
		MaxDomains: req.MaxDomains,
		SortOrder:  req.SortOrder,
	}

	_, err := db.GetEngine(ctx).Insert(tier)
	if err != nil {
		ctx.APIError(http.StatusInternalServerError, "failed to create tier")
		return
	}

	ctx.JSON(http.StatusCreated, tierToJSON(tier))
}

type updateTierRequest struct {
	TierName   *string  `json:"tier_name"`
	Repos      []string `json:"repos"`
	MaxDomains *int     `json:"max_domains"`
	SortOrder  *int     `json:"sort_order"`
}

// UpdateTier handles PATCH /api/v1/licensing/tiers/{id}
func UpdateTier(ctx *context.APIContext) {
	id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
	if err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid tier ID")
		return
	}

	tier := new(licensing_model.ProductTier)
	has, err := db.GetEngine(ctx).ID(id).Get(tier)
	if err != nil || !has {
		ctx.APIErrorNotFound()
		return
	}

	var req updateTierRequest
	if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid request body")
		return
	}

	cols := make([]string, 0)
	if req.TierName != nil {
		tier.TierName = *req.TierName
		cols = append(cols, "tier_name")
	}
	if req.Repos != nil {
		reposJSON, _ := mojo_json.Marshal(req.Repos)
		tier.Repos = string(reposJSON)
		cols = append(cols, "repos")
	}
	if req.MaxDomains != nil {
		tier.MaxDomains = *req.MaxDomains
		cols = append(cols, "max_domains")
	}
	if req.SortOrder != nil {
		tier.SortOrder = *req.SortOrder
		cols = append(cols, "sort_order")
	}

	if len(cols) > 0 {
		db.GetEngine(ctx).ID(id).Cols(cols...).Update(tier)
	}

	ctx.JSON(http.StatusOK, tierToJSON(tier))
}

// DeleteTier handles DELETE /api/v1/licensing/tiers/{id}
func DeleteTier(ctx *context.APIContext) {
	id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
	if err != nil {
		ctx.APIError(http.StatusBadRequest, "invalid tier ID")
		return
	}

	// Check if any licenses use this tier
	tier := new(licensing_model.ProductTier)
	has, _ := db.GetEngine(ctx).ID(id).Get(tier)
	if !has {
		ctx.APIErrorNotFound()
		return
	}

	count, _ := db.GetEngine(ctx).Where("tier = ?", tier.TierKey).Count(new(licensing_model.License))
	if count > 0 {
		ctx.APIError(http.StatusConflict, "cannot delete tier with active licenses")
		return
	}

	db.GetEngine(ctx).ID(id).Delete(new(licensing_model.ProductTier))
	ctx.Status(http.StatusNoContent)
}

// ── Helpers ─────────────────────────────────────────────────────────────

func licenseToJSON(ctx *context.APIContext, l *licensing_model.License) map[string]any {
	tierName := l.Tier
	tier, _ := licensing_model.GetProductTierByKey(ctx, l.Tier)
	if tier != nil {
		tierName = tier.TierName
	}
	domainCount, _ := licensing_model.CountActivations(ctx, l.ID)

	result := map[string]any{
		"id":           l.ID,
		"user_id":      l.UserID,
		"dlid":         l.DLID,
		"tier":         l.Tier,
		"tier_name":    tierName,
		"max_domains":  l.MaxDomains,
		"domains_used": domainCount,
		"status":       l.Status,
		"notes":        l.Notes,
		"created_at":   formatTime(l.CreatedAt),
		"updated_at":   formatTime(l.UpdatedAt),
	}
	if l.ExpiresAt > 0 {
		result["expires_at"] = formatTime(l.ExpiresAt)
	}
	return result
}

func tierToJSON(t *licensing_model.ProductTier) map[string]any {
	return map[string]any{
		"id":          t.ID,
		"tier_key":    t.TierKey,
		"tier_name":   t.TierName,
		"repos":       t.RepoList(),
		"max_domains": t.MaxDomains,
		"sort_order":  t.SortOrder,
	}
}

func formatTime(ts timeutil.TimeStamp) string {
	if ts == 0 {
		return ""
	}
	return time.Unix(int64(ts), 0).UTC().Format(time.RFC3339)
}
