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

package updateserver

import (
	"context"
	"fmt"
	"html"
	"strings"
	"time"

	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
	updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
	repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
)

// WordPressPluginInfo is the JSON response format compatible with the
// YahnisElsts plugin-update-checker (PUC) library — the standard for
// commercial WordPress plugin self-hosted updates.
type WordPressPluginInfo struct {
	Name           string                       `json:"name"`
	Slug           string                       `json:"slug"`
	Version        string                       `json:"version"`
	DownloadURL    string                       `json:"download_url,omitempty"`
	Homepage       string                       `json:"homepage,omitempty"`
	Requires       string                       `json:"requires,omitempty"`
	Tested         string                       `json:"tested,omitempty"`
	RequiresPHP    string                       `json:"requires_php,omitempty"`
	Author         string                       `json:"author,omitempty"`
	AuthorHomepage string                       `json:"author_homepage,omitempty"`
	LastUpdated    string                       `json:"last_updated,omitempty"`
	UpgradeNotice  string                       `json:"upgrade_notice,omitempty"`
	Sections       map[string]string            `json:"sections,omitempty"`
	Icons          map[string]string            `json:"icons,omitempty"`
	Banners        map[string]string            `json:"banners,omitempty"`
}

// GenerateWordPressJSON builds a PUC-compatible JSON response for the latest
// stable release. The license key (if provided) is embedded in the download URL.
func GenerateWordPressJSON(ctx context.Context, repo *repo_model.Repository, licenseKey string) (*WordPressPluginInfo, error) {
	releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
		RepoID:        repo.ID,
		ListOptions:   db.ListOptionsAll,
		IncludeDrafts: false,
		IncludeTags:   false,
	})
	if err != nil {
		return nil, fmt.Errorf("FindReleases: %w", err)
	}

	if err := repo.LoadOwner(ctx); err != nil {
		return nil, fmt.Errorf("LoadOwner: %w", err)
	}

	baseURL := strings.TrimSuffix(setting.AppURL, "/")
	repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)

	// Load extension metadata with cascading fallback:
	// custom fields → config table → repo-derived defaults.
	cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
	meta := resolveExtensionMetadata(ctx, repo, cfg)

	slug := meta.Element
	displayName := meta.DisplayName
	requiresPHP := meta.PHPMinimum
	homepage := repoLink
	if meta.SupportURL != "" {
		homepage = meta.SupportURL
	}
	maintainer := repo.Owner.Name
	maintainerURL := fmt.Sprintf("%s/%s", baseURL, repo.Owner.Name)
	if cfg != nil {
		if cfg.Maintainer != "" {
			maintainer = cfg.Maintainer
		}
		if cfg.MaintainerURL != "" {
			maintainerURL = cfg.MaintainerURL
		}
		if homepage == repoLink && cfg.InfoURL != "" {
			homepage = cfg.InfoURL
		}
	}

	// Resolve streams and find the latest stable release.
	streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
	var latestStable *repo_model.Release
	for _, rel := range releases {
		if rel.IsDraft || rel.IsTag {
			continue
		}
		ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
		if ch == "stable" {
			if latestStable == nil || rel.CreatedUnix > latestStable.CreatedUnix {
				latestStable = rel
			}
		}
	}

	if latestStable == nil {
		return &WordPressPluginInfo{
			Name:    displayName,
			Slug:    slug,
			Version: "0.0.0",
		}, nil
	}

	// Load attachments.
	if err := latestStable.LoadAttributes(ctx); err != nil {
		return nil, fmt.Errorf("LoadAttributes: %w", err)
	}

	// Find zip download URL.
	var downloadURL string
	for _, att := range latestStable.Attachments {
		if strings.HasSuffix(strings.ToLower(att.Name), ".zip") && !strings.HasSuffix(att.Name, ".sha256") {
			downloadURL = fmt.Sprintf("%s/releases/download/%s/%s", repoLink, latestStable.TagName, att.Name)
			break
		}
	}
	if downloadURL == "" {
		downloadURL = fmt.Sprintf("%s/archive/%s.zip", repoLink, latestStable.TagName)
	}

	// Append license key to download URL if provided.
	if licenseKey != "" {
		if strings.Contains(downloadURL, "?") {
			downloadURL += "&dlid=" + licenseKey
		} else {
			downloadURL += "?dlid=" + licenseKey
		}
	}

	version := extractVersion(latestStable.TagName)
	if version == "" || isStreamName(latestStable.TagName, streams) {
		version = extractVersion(latestStable.Title)
	}
	if version == "" {
		version = latestStable.TagName
	}
	lastUpdated := time.Unix(int64(latestStable.CreatedUnix), 0).Format("2006-01-02 3:04pm MST")

	// Build sections from release notes.
	sections := map[string]string{}
	if latestStable.Note != "" {
		sections["changelog"] = buildWordPressChangelog(ctx, releases, streams)
	}
	if cfg != nil && cfg.Description != "" {
		sections["description"] = "<p>" + html.EscapeString(cfg.Description) + "</p>"
	}

	// Build icon/banner URLs from repo assets.
	icons := buildAssetURLs(repoLink, repo.DefaultBranch, "icon", []string{"128x128", "256x256"})
	banners := buildBannerURLs(repoLink, repo.DefaultBranch)

	return &WordPressPluginInfo{
		Name:           displayName,
		Slug:           slug,
		Version:        version,
		DownloadURL:    downloadURL,
		Homepage:       homepage,
		RequiresPHP:    requiresPHP,
		Author:         maintainer,
		AuthorHomepage: maintainerURL,
		LastUpdated:    lastUpdated,
		Sections:       sections,
		Icons:          icons,
		Banners:        banners,
	}, nil
}

// buildWordPressChangelog builds an HTML changelog from multiple releases.
func buildWordPressChangelog(ctx context.Context, releases []*repo_model.Release, streams []updateserver_model.StreamDef) string {
	var b strings.Builder
	count := 0
	for _, rel := range releases {
		if rel.IsDraft || rel.IsTag || rel.Note == "" {
			continue
		}
		ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
		if ch != "stable" {
			continue
		}
		version := extractVersion(rel.TagName)
		date := time.Unix(int64(rel.CreatedUnix), 0).Format("2006-01-02")
		b.WriteString(fmt.Sprintf("<h4>%s - %s</h4>\n", version, date))

		// Convert markdown list items to HTML list.
		lines := strings.Split(rel.Note, "\n")
		b.WriteString("<ul>\n")
		for _, line := range lines {
			trimmed := strings.TrimSpace(line)
			if strings.HasPrefix(trimmed, "- ") || strings.HasPrefix(trimmed, "* ") {
				b.WriteString(fmt.Sprintf("<li>%s</li>\n", html.EscapeString(strings.TrimLeft(trimmed[2:], " "))))
			}
		}
		b.WriteString("</ul>\n")

		count++
		if count >= 10 {
			break // limit to last 10 releases
		}
	}
	return b.String()
}

// buildAssetURLs generates icon URLs from conventional paths in the repo.
func buildAssetURLs(repoLink, branch, prefix string, sizes []string) map[string]string {
	icons := make(map[string]string)
	for i, size := range sizes {
		key := fmt.Sprintf("%dx", i+1)
		icons[key] = fmt.Sprintf("%s/raw/branch/%s/assets/%s-%s.png", repoLink, branch, prefix, size)
	}
	return icons
}

// buildBannerURLs generates banner URLs from conventional paths.
func buildBannerURLs(repoLink, branch string) map[string]string {
	return map[string]string{
		"low":  fmt.Sprintf("%s/raw/branch/%s/assets/banner-772x250.png", repoLink, branch),
		"high": fmt.Sprintf("%s/raw/branch/%s/assets/banner-1544x500.png", repoLink, branch),
	}
}
