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

package updateserver

import (
	"context"
	"fmt"
	"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"
)

// ComposerPackage represents the packages.json response for Composer/Packagist.
type ComposerPackage struct {
	Packages map[string]map[string]ComposerVersion `json:"packages"`
}

// ComposerVersion represents a single version entry.
type ComposerVersion struct {
	Name        string            `json:"name"`
	Version     string            `json:"version"`
	Type        string            `json:"type,omitempty"`
	Description string            `json:"description,omitempty"`
	Homepage    string            `json:"homepage,omitempty"`
	License     []string          `json:"license,omitempty"`
	Authors     []ComposerAuthor  `json:"authors,omitempty"`
	Dist        *ComposerDist     `json:"dist,omitempty"`
	Require     map[string]string `json:"require,omitempty"`
	Time        string            `json:"time,omitempty"`
}

// ComposerAuthor represents a package author.
type ComposerAuthor struct {
	Name     string `json:"name"`
	Homepage string `json:"homepage,omitempty"`
}

// ComposerDist represents a distribution archive.
type ComposerDist struct {
	URL       string `json:"url"`
	Type      string `json:"type"`
	Shasum    string `json:"shasum,omitempty"`
	Reference string `json:"reference,omitempty"`
}

// GenerateComposerJSON builds a Composer packages.json from repo releases.
func GenerateComposerJSON(ctx context.Context, repo *repo_model.Repository, licenseKey string) (*ComposerPackage, 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)

	cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
	meta := resolveExtensionMetadata(ctx, repo, cfg)

	// Composer package name: vendor/package (override with resolved extension name if set)
	packageName := fmt.Sprintf("%s/%s", strings.ToLower(repo.Owner.Name), strings.ToLower(repo.Name))
	if meta.Element != strings.ToLower(repo.Name) {
		packageName = meta.Element
	}

	description := meta.Description
	maintainer := repo.Owner.Name
	maintainerURL := fmt.Sprintf("%s/%s", baseURL, repo.Owner.Name)
	if cfg != nil && cfg.Maintainer != "" {
		maintainer = cfg.Maintainer
	}
	if cfg != nil && cfg.MaintainerURL != "" {
		maintainerURL = cfg.MaintainerURL
	}

	phpMin := ""
	if meta.PHPMinimum != "" {
		phpMin = ">=" + meta.PHPMinimum
	}

	streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
	versions := make(map[string]ComposerVersion)

	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" {
			continue // Composer only serves stable versions
		}

		version := extractVersion(rel.TagName)
		if isStreamName(version, streams) {
			version = extractVersion(rel.Title)
		}
		if version == "" {
			continue
		}

		if err := rel.LoadAttributes(ctx); err != nil {
			continue
		}

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

		// Look for SHA256 sidecar
		for _, att := range rel.Attachments {
			if strings.HasSuffix(att.Name, ".sha256") {
				sha256Hash = readSHA256FromSidecar(ctx, att)
				break
			}
		}

		require := make(map[string]string)
		if phpMin != "" {
			require["php"] = phpMin
		}

		v := ComposerVersion{
			Name:        packageName,
			Version:     version,
			Description: description,
			Homepage:    repoLink,
			Authors: []ComposerAuthor{
				{Name: maintainer, Homepage: maintainerURL},
			},
			Dist: &ComposerDist{
				URL:       downloadURL,
				Type:      "zip",
				Shasum:    sha256Hash,
				Reference: rel.TagName,
			},
			Require: require,
			Time:    time.Unix(int64(rel.CreatedUnix), 0).Format(time.RFC3339),
		}

		versions[version] = v
	}

	return &ComposerPackage{
		Packages: map[string]map[string]ComposerVersion{
			packageName: versions,
		},
	}, nil
}
