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

package setting

import (
	"encoding/xml"
	"fmt"
	"net/http"

	repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)

const tplSettingsManifest templates.TplName = "repo/settings/manifest"

// manifestXML mirrors the .mokogitea/manifest.xml schema for XML parsing.
type manifestXML struct {
	XMLName      xml.Name             `xml:"mokoplatform"`
	Identity     manifestIdentity     `xml:"identity"`
	Governance   manifestGovernance   `xml:"governance"`
	Distribution manifestDistribution `xml:"distribution"`
	Build        manifestBuild        `xml:"build"`
}

type manifestIdentity struct {
	Name          string          `xml:"name"`
	Org           string          `xml:"org"`
	Description   string          `xml:"description"`
	Version       string          `xml:"version"`
	VersionPrefix string          `xml:"version-prefix"`
	License       manifestLicense `xml:"license"`
}

type manifestLicense struct {
	SPDX string `xml:"spdx,attr"`
	Name string `xml:",chardata"`
}

type manifestGovernance struct {
	Platform         string `xml:"platform"`
	StandardsVersion string `xml:"standards-version"`
	StandardsSource  string `xml:"standards-source"`
}

type manifestDistribution struct {
	DisplayName   string `xml:"display-name"`
	Maintainer    string `xml:"maintainer"`
	MaintainerURL string `xml:"maintainer-url"`
	InfoURL       string `xml:"info-url"`
	TargetVersion string `xml:"target-version"`
	PHPMinimum    string `xml:"php-minimum"`
}

type manifestBuild struct {
	Language    string `xml:"language"`
	PackageType string `xml:"package-type"`
	EntryPoint  string `xml:"entry-point"`
}

// ManifestSettings displays the repo manifest settings page.
// On first visit, if no manifest exists in DB but .mokogitea/manifest.xml
// exists in the repo, it auto-migrates the XML values into the database.
func ManifestSettings(ctx *context.Context) {
	ctx.Data["Title"] = ctx.Tr("repo.settings.manifest")
	ctx.Data["PageIsSettingsManifest"] = true

	repoID := ctx.Repo.Repository.ID
	manifest, err := repo_model.GetRepoManifest(ctx, repoID)
	if err != nil {
		ctx.ServerError("GetRepoManifest", err)
		return
	}

	// Auto-detect and migrate .mokogitea/manifest.xml if no DB record exists.
	if manifest == nil {
		manifest = tryMigrateManifestXML(ctx)
	}

	if manifest == nil {
		// No manifest found — provide empty defaults from repo metadata.
		manifest = &repo_model.RepoManifest{
			RepoID:      repoID,
			Name:        ctx.Repo.Repository.Name,
			Org:         ctx.Repo.Repository.OwnerName,
			Description: ctx.Repo.Repository.Description,
		}
	}

	ctx.Data["Manifest"] = manifest
	ctx.HTML(http.StatusOK, tplSettingsManifest)
}

// ManifestSettingsPost saves manifest settings from the form.
func ManifestSettingsPost(ctx *context.Context) {
	manifest := &repo_model.RepoManifest{
		RepoID:           ctx.Repo.Repository.ID,
		Name:             ctx.FormString("name"),
		Org:              ctx.FormString("org"),
		Description:      ctx.Repo.Repository.Description,
		Version:          ctx.FormString("version"),
		LicenseSPDX:      ctx.FormString("license_spdx"),
		LicenseName:      ctx.FormString("license_name"),
		VersionPrefix:    ctx.FormString("version_prefix"),
		ElementName:      ctx.FormString("element_name"),
		Platform:         ctx.FormString("platform"),
		StandardsVersion: ctx.FormString("standards_version"),
		StandardsSource:  ctx.FormString("standards_source"),
		DisplayName:      ctx.FormString("display_name"),
		Maintainer:       ctx.FormString("maintainer"),
		MaintainerURL:    ctx.FormString("maintainer_url"),
		InfoURL:          ctx.FormString("info_url"),
		TargetVersion:    ctx.FormString("target_version"),
		PHPMinimum:       ctx.FormString("php_minimum"),
		Language:         ctx.FormString("language"),
		PackageType:      ctx.FormString("package_type"),
		EntryPoint:       ctx.FormString("entry_point"),
	}

	if err := repo_model.CreateOrUpdateRepoManifest(ctx, manifest); err != nil {
		ctx.ServerError("CreateOrUpdateRepoManifest", err)
		return
	}

	ctx.Flash.Success(ctx.Tr("repo.settings.manifest_saved"))
	ctx.Redirect(ctx.Repo.RepoLink + "/settings/manifest")
}

// tryMigrateManifestXML reads .mokogitea/manifest.xml from the repo,
// parses it, and stores the values in the DB. Returns nil if no file found.
func tryMigrateManifestXML(ctx *context.Context) *repo_model.RepoManifest {
	if ctx.Repo.GitRepo == nil || ctx.Repo.Commit == nil {
		return nil
	}

	entry, err := ctx.Repo.Commit.GetTreeEntryByPath(".mokogitea/manifest.xml")
	if err != nil || entry == nil {
		return nil // no manifest.xml found — not an error
	}

	reader, err := entry.Blob().DataAsync()
	if err != nil {
		log.Error("ManifestMigrate: read blob: %v", err)
		return nil
	}
	defer reader.Close()

	var mxml manifestXML
	if err := xml.NewDecoder(reader).Decode(&mxml); err != nil {
		log.Error("ManifestMigrate: parse XML: %v", err)
		return nil
	}

	manifest := &repo_model.RepoManifest{
		RepoID:           ctx.Repo.Repository.ID,
		Name:             mxml.Identity.Name,
		Org:              mxml.Identity.Org,
		Description:      mxml.Identity.Description,
		Version:          mxml.Identity.Version,
		LicenseSPDX:      mxml.Identity.License.SPDX,
		LicenseName:      mxml.Identity.License.Name,
		VersionPrefix:    mxml.Identity.VersionPrefix,
		Platform:         mxml.Governance.Platform,
		StandardsVersion: mxml.Governance.StandardsVersion,
		StandardsSource:  mxml.Governance.StandardsSource,
		DisplayName:      mxml.Distribution.DisplayName,
		Maintainer:       mxml.Distribution.Maintainer,
		MaintainerURL:    mxml.Distribution.MaintainerURL,
		InfoURL:          mxml.Distribution.InfoURL,
		TargetVersion:    mxml.Distribution.TargetVersion,
		PHPMinimum:       mxml.Distribution.PHPMinimum,
		Language:         mxml.Build.Language,
		PackageType:      mxml.Build.PackageType,
		EntryPoint:       mxml.Build.EntryPoint,
	}

	if err := repo_model.CreateOrUpdateRepoManifest(ctx, manifest); err != nil {
		log.Error("ManifestMigrate: save to DB: %v", err)
		return nil
	}

	log.Info("ManifestMigrate: migrated .mokogitea/manifest.xml for repo %s/%s",
		ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name)

	ctx.Flash.Info(fmt.Sprintf("Manifest settings imported from .mokogitea/manifest.xml. You can now delete the file from the repository."))
	return manifest
}
