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

package updateserver

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

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

// PrestaShop module update XML structures.

type psUpdates struct {
	XMLName xml.Name   `xml:"modules"`
	Modules []psModule `xml:"module"`
}

type psModule struct {
	Name        string `xml:"name,attr"`
	Version     string `xml:"version,attr"`
	DisplayName string `xml:"displayName"`
	Description string `xml:"description,omitempty"`
	Author      string `xml:"author"`
	Tab         string `xml:"tab,omitempty"`
	Download    string `xml:"download"`
	Date        string `xml:"date"`
	SHA256      string `xml:"sha256,omitempty"`
}

// GeneratePrestaShopXML builds a PrestaShop-compatible module update XML.
func GeneratePrestaShopXML(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)
	moduleName := meta.Element
	displayName := meta.DisplayName
	description := meta.Description
	maintainer := repo.Owner.Name
	if cfg != nil && cfg.Maintainer != "" {
		maintainer = cfg.Maintainer
	}

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

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

	// Track best release per channel.
	bestByChannel := make(map[string]*repo_model.Release)
	for _, rel := range releases {
		if rel.IsDraft || rel.IsTag {
			continue
		}
		ch := licenses.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
		existing, ok := bestByChannel[ch]
		if !ok || rel.CreatedUnix > existing.CreatedUnix {
			bestByChannel[ch] = rel
		}
	}

	var result psUpdates
	for _, stream := range streams {
		ch := stream.Name
		if len(channelAllowed) > 0 && !channelAllowed[ch] {
			continue
		}
		rel, ok := bestByChannel[ch]
		if !ok || ch != "stable" {
			continue // PrestaShop typically only serves stable
		}

		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
		}

		result.Modules = append(result.Modules, psModule{
			Name:        moduleName,
			Version:     version,
			DisplayName: displayName,
			Description: description,
			Author:      maintainer,
			Download:    downloadURL,
			Date:        time.Unix(int64(rel.CreatedUnix), 0).Format("2006-01-02"),
			SHA256:      sha256Hash,
		})
	}

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

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