package skillbundle

import (
	"archive/zip"
	"bytes"
	"embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"path"
	"regexp"
	"sort"
	"strings"
	"time"
)

const (
	Name        = "ai-gateway-ops-management"
	DisplayName = "AI Gateway 运维管理"
)

const bundleRoot = "content/skills/" + Name

var semverPattern = regexp.MustCompile(`^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$`)

//go:embed content/skills/ai-gateway-ops-management
var bundleFS embed.FS

type Metadata struct {
	Name    string   `json:"name"`
	Version string   `json:"version"`
	Modules []string `json:"modules"`
}

func LoadMetadata() (Metadata, error) {
	raw, err := bundleFS.ReadFile(bundleRoot + "/skill.json")
	if err != nil {
		return Metadata{}, fmt.Errorf("read bundled skill metadata: %w", err)
	}
	var metadata Metadata
	if err := json.Unmarshal(raw, &metadata); err != nil {
		return Metadata{}, fmt.Errorf("parse bundled skill metadata: %w", err)
	}
	if metadata.Name != Name {
		return Metadata{}, fmt.Errorf("bundled skill metadata name must be %q", Name)
	}
	if !semverPattern.MatchString(metadata.Version) {
		return Metadata{}, fmt.Errorf("bundled skill metadata version %q is not semver", metadata.Version)
	}
	if len(metadata.Modules) == 0 {
		return Metadata{}, fmt.Errorf("bundled skill metadata modules must not be empty")
	}
	seen := make(map[string]struct{}, len(metadata.Modules))
	for _, module := range metadata.Modules {
		module = strings.TrimSpace(module)
		if module == "" {
			return Metadata{}, fmt.Errorf("bundled skill metadata module must not be empty")
		}
		if _, exists := seen[module]; exists {
			return Metadata{}, fmt.Errorf("bundled skill metadata module %q is duplicated", module)
		}
		seen[module] = struct{}{}
	}
	if _, err := bundleFS.ReadFile(bundleRoot + "/SKILL.md"); err != nil {
		return Metadata{}, fmt.Errorf("read bundled SKILL.md: %w", err)
	}
	return metadata, nil
}

func FileName(metadata Metadata) string {
	return fmt.Sprintf("%s-v%s.zip", metadata.Name, metadata.Version)
}

func BuildArchive() ([]byte, error) {
	if _, err := LoadMetadata(); err != nil {
		return nil, err
	}
	files := make([]string, 0)
	if err := fs.WalkDir(bundleFS, bundleRoot, func(filePath string, entry fs.DirEntry, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if entry.IsDir() {
			return nil
		}
		relativePath := strings.TrimPrefix(filePath, bundleRoot+"/")
		if relativePath == "" || path.IsAbs(relativePath) || path.Clean(relativePath) != relativePath || strings.HasPrefix(relativePath, "../") {
			return fmt.Errorf("unsafe bundled skill path %q", relativePath)
		}
		files = append(files, relativePath)
		return nil
	}); err != nil {
		return nil, fmt.Errorf("walk bundled skill: %w", err)
	}
	sort.Strings(files)

	var output bytes.Buffer
	archive := zip.NewWriter(&output)
	fixedTime := time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
	for _, relativePath := range files {
		raw, err := bundleFS.ReadFile(bundleRoot + "/" + relativePath)
		if err != nil {
			_ = archive.Close()
			return nil, fmt.Errorf("read bundled skill file %q: %w", relativePath, err)
		}
		header := &zip.FileHeader{Name: relativePath, Method: zip.Deflate}
		header.SetModTime(fixedTime)
		writer, err := archive.CreateHeader(header)
		if err != nil {
			_ = archive.Close()
			return nil, fmt.Errorf("create bundled skill archive entry %q: %w", relativePath, err)
		}
		if _, err := writer.Write(raw); err != nil {
			_ = archive.Close()
			return nil, fmt.Errorf("write bundled skill archive entry %q: %w", relativePath, err)
		}
	}
	if err := archive.Close(); err != nil {
		return nil, fmt.Errorf("close bundled skill archive: %w", err)
	}
	return output.Bytes(), nil
}
