// 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"
)

// DolibarrUpdate represents a single module update entry in Dolibarr format.
type DolibarrUpdate struct {
	Name         string `json:"name"`
	Version      string `json:"version"`
	Channel      string `json:"channel"`
	DownloadURL  string `json:"url"`
	ChangelogURL string `json:"changelog"`
	ReleaseURL   string `json:"release_url"`
	Requires     string `json:"requires,omitempty"`
	Date         string `json:"date"`
	SHA256       string `json:"sha256,omitempty"`
}

// DolibarrUpdates holds the full update feed response.
type DolibarrUpdates struct {
	Module  string           `json:"module"`
	Updates []DolibarrUpdate `json:"updates"`
}

// GenerateDolibarrJSON builds a Dolibarr-compatible update feed from releases.
// allowedChannels optionally restricts output to specific channels (nil = all).
func GenerateDolibarrJSON(ctx context.Context, repo *repo_model.Repository, allowedChannels ...string) (*DolibarrUpdates, 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)

	result := &DolibarrUpdates{
		Module: repo.Name,
	}

	// Resolve effective streams.
	streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)

	// Track best release per channel.
	bestByChannel := make(map[string]*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)
		existing, ok := bestByChannel[ch]
		if !ok || rel.CreatedUnix > existing.CreatedUnix {
			bestByChannel[ch] = rel
		}
	}

	// Build allowed channel set for filtering.
	channelAllowed := make(map[string]bool)
	if len(allowedChannels) > 0 {
		for _, c := range allowedChannels {
			channelAllowed[NormalizeChannel(c)] = true
		}
	}

	for _, stream := range streams {
		ch := stream.Name
		if len(channelAllowed) > 0 && !channelAllowed[ch] {
			continue
		}
		rel, ok := bestByChannel[ch]
		if !ok {
			continue
		}

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

		var downloadURL string
		for _, att := range rel.Attachments {
			if strings.HasSuffix(strings.ToLower(att.Name), ".zip") {
				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)
		}

		version := extractVersion(rel.TagName)
		if version == "" || isStreamName(rel.TagName, streams) {
			version = extractVersion(rel.Title)
		}
		if version == "" {
			version = rel.TagName
		}
		suffix := stream.Suffix
		if suffix == "" {
			suffix = channelSuffix(ch)
		}
		if suffix != "" {
			version = version + suffix
		}

		result.Updates = append(result.Updates, DolibarrUpdate{
			Name:         repo.Name,
			Version:      version,
			Channel:      ch,
			DownloadURL:  downloadURL,
			ChangelogURL: fmt.Sprintf("%s/raw/branch/%s/CHANGELOG.md", repoLink, repo.DefaultBranch),
			ReleaseURL:   fmt.Sprintf("%s/releases/tag/%s", repoLink, rel.TagName),
			Date:         time.Unix(int64(rel.CreatedUnix), 0).Format("2006-01-02"),
		})
	}

	return result, nil
}
