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

package updateserver

import (
	"context"
	"encoding/xml"
	"fmt"
	"strings"

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

// Drupal update status XML structures.
// See: https://www.drupal.org/docs/drupal-apis/update-status-module/update-status-xml-format

type drupalProject struct {
	XMLName        xml.Name        `xml:"project"`
	Title          string          `xml:"title"`
	ShortName      string          `xml:"short_name"`
	APIVersion     string          `xml:"api_version"`
	RecommendedMaj string          `xml:"recommended_major"`
	DefaultMajor   string          `xml:"default_major"`
	ProjectStatus  string          `xml:"project_status"`
	Link           string          `xml:"link"`
	Releases       drupalReleases  `xml:"releases"`
}

type drupalReleases struct {
	Releases []drupalRelease `xml:"release"`
}

type drupalRelease struct {
	Name        string `xml:"name"`
	Version     string `xml:"version"`
	Tag         string `xml:"tag"`
	Status      string `xml:"status"`
	ReleaseLink string `xml:"release_link"`
	DownloadURL string `xml:"download_link"`
	Date        string `xml:"date"`
	FileHash    string `xml:"mdhash,omitempty"`
	SHA256      string `xml:"sha256,omitempty"`
}

// GenerateDrupalXML builds a Drupal update status-compatible XML feed.
func GenerateDrupalXML(ctx context.Context, repo *repo_model.Repository, allowedChannels ...string) ([]byte, 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 := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
	meta := resolveExtensionMetadata(ctx, repo, cfg)
	shortName := meta.Element
	title := meta.DisplayName

	streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)

	channelAllowed := make(map[string]bool)
	if len(allowedChannels) > 0 {
		for _, c := range allowedChannels {
			channelAllowed[NormalizeChannel(c)] = true
		}
	}

	project := drupalProject{
		Title:          title,
		ShortName:      shortName,
		APIVersion:     "7.x", // default API version
		RecommendedMaj: "1",
		DefaultMajor:   "1",
		ProjectStatus:  "published",
		Link:           repoLink,
	}

	if cfg != nil && cfg.TargetVersion != "" {
		project.APIVersion = cfg.TargetVersion
	}

	for _, rel := range releases {
		if rel.IsDraft || rel.IsTag {
			continue
		}
		ch := licenses.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
		if len(channelAllowed) > 0 && !channelAllowed[ch] {
			continue
		}

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

		var downloadURL, 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)
		}
		for _, att := range rel.Attachments {
			if strings.HasSuffix(att.Name, ".sha256") {
				sha256Hash = readSHA256FromSidecar(ctx, att)
				break
			}
		}

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

		status := "published"
		if rel.IsPrerelease {
			status = "insecure" // Drupal uses this for non-stable
		}

		project.Releases.Releases = append(project.Releases.Releases, drupalRelease{
			Name:        fmt.Sprintf("%s %s", shortName, version),
			Version:     version,
			Tag:         rel.TagName,
			Status:      status,
			ReleaseLink: fmt.Sprintf("%s/releases/tag/%s", repoLink, rel.TagName),
			DownloadURL: downloadURL,
			Date:        fmt.Sprintf("%d", rel.CreatedUnix),
			SHA256:      sha256Hash,
		})
	}

	output, err := xml.MarshalIndent(project, "", "  ")
	if err != nil {
		return nil, fmt.Errorf("xml.MarshalIndent: %w", err)
	}

	return append([]byte(xml.Header), output...), nil
}
