// Package publishers provides release publishing implementations.
package publishers

import (
	"bytes"
	"context"
	"embed"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/host-uk/core/pkg/build"
	"github.com/host-uk/core/pkg/io"
)

//go:embed templates/scoop/*.tmpl
var scoopTemplates embed.FS

// ScoopConfig holds Scoop-specific configuration.
type ScoopConfig struct {
	// Bucket is the Scoop bucket repository (e.g., "host-uk/scoop-bucket").
	Bucket string
	// Official config for generating files for official repo PRs.
	Official *OfficialConfig
}

// ScoopPublisher publishes releases to Scoop.
type ScoopPublisher struct{}

// NewScoopPublisher creates a new Scoop publisher.
func NewScoopPublisher() *ScoopPublisher {
	return &ScoopPublisher{}
}

// Name returns the publisher's identifier.
func (p *ScoopPublisher) Name() string {
	return "scoop"
}

// Publish publishes the release to Scoop.
func (p *ScoopPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
	cfg := p.parseConfig(pubCfg, relCfg)

	if cfg.Bucket == "" && (cfg.Official == nil || !cfg.Official.Enabled) {
		return fmt.Errorf("scoop.Publish: bucket is required (set publish.scoop.bucket in config)")
	}

	repo := ""
	if relCfg != nil {
		repo = relCfg.GetRepository()
	}
	if repo == "" {
		detectedRepo, err := detectRepository(release.ProjectDir)
		if err != nil {
			return fmt.Errorf("scoop.Publish: could not determine repository: %w", err)
		}
		repo = detectedRepo
	}

	projectName := ""
	if relCfg != nil {
		projectName = relCfg.GetProjectName()
	}
	if projectName == "" {
		parts := strings.Split(repo, "/")
		projectName = parts[len(parts)-1]
	}

	version := strings.TrimPrefix(release.Version, "v")
	checksums := buildChecksumMap(release.Artifacts)

	data := scoopTemplateData{
		PackageName: projectName,
		Description: fmt.Sprintf("%s CLI", projectName),
		Repository:  repo,
		Version:     version,
		License:     "MIT",
		BinaryName:  projectName,
		Checksums:   checksums,
	}

	if dryRun {
		return p.dryRunPublish(release.FS, data, cfg)
	}

	return p.executePublish(ctx, release.ProjectDir, data, cfg, release)
}

type scoopTemplateData struct {
	PackageName string
	Description string
	Repository  string
	Version     string
	License     string
	BinaryName  string
	Checksums   ChecksumMap
}

func (p *ScoopPublisher) parseConfig(pubCfg PublisherConfig, relCfg ReleaseConfig) ScoopConfig {
	cfg := ScoopConfig{}

	if ext, ok := pubCfg.Extended.(map[string]any); ok {
		if bucket, ok := ext["bucket"].(string); ok && bucket != "" {
			cfg.Bucket = bucket
		}
		if official, ok := ext["official"].(map[string]any); ok {
			cfg.Official = &OfficialConfig{}
			if enabled, ok := official["enabled"].(bool); ok {
				cfg.Official.Enabled = enabled
			}
			if output, ok := official["output"].(string); ok {
				cfg.Official.Output = output
			}
		}
	}

	return cfg
}

func (p *ScoopPublisher) dryRunPublish(m io.Medium, data scoopTemplateData, cfg ScoopConfig) error {
	fmt.Println()
	fmt.Println("=== DRY RUN: Scoop Publish ===")
	fmt.Println()
	fmt.Printf("Package:    %s\n", data.PackageName)
	fmt.Printf("Version:    %s\n", data.Version)
	fmt.Printf("Bucket:     %s\n", cfg.Bucket)
	fmt.Printf("Repository: %s\n", data.Repository)
	fmt.Println()

	manifest, err := p.renderTemplate(m, "templates/scoop/manifest.json.tmpl", data)
	if err != nil {
		return fmt.Errorf("scoop.dryRunPublish: %w", err)
	}
	fmt.Println("Generated manifest.json:")
	fmt.Println("---")
	fmt.Println(manifest)
	fmt.Println("---")
	fmt.Println()

	if cfg.Bucket != "" {
		fmt.Printf("Would commit to bucket: %s\n", cfg.Bucket)
	}
	if cfg.Official != nil && cfg.Official.Enabled {
		output := cfg.Official.Output
		if output == "" {
			output = "dist/scoop"
		}
		fmt.Printf("Would write files for official PR to: %s\n", output)
	}
	fmt.Println()
	fmt.Println("=== END DRY RUN ===")

	return nil
}

func (p *ScoopPublisher) executePublish(ctx context.Context, projectDir string, data scoopTemplateData, cfg ScoopConfig, release *Release) error {
	manifest, err := p.renderTemplate(release.FS, "templates/scoop/manifest.json.tmpl", data)
	if err != nil {
		return fmt.Errorf("scoop.Publish: failed to render manifest: %w", err)
	}

	// If official config is enabled, write to output directory
	if cfg.Official != nil && cfg.Official.Enabled {
		output := cfg.Official.Output
		if output == "" {
			output = filepath.Join(projectDir, "dist", "scoop")
		} else if !filepath.IsAbs(output) {
			output = filepath.Join(projectDir, output)
		}

		if err := release.FS.EnsureDir(output); err != nil {
			return fmt.Errorf("scoop.Publish: failed to create output directory: %w", err)
		}

		manifestPath := filepath.Join(output, fmt.Sprintf("%s.json", data.PackageName))
		if err := release.FS.Write(manifestPath, manifest); err != nil {
			return fmt.Errorf("scoop.Publish: failed to write manifest: %w", err)
		}
		fmt.Printf("Wrote Scoop manifest for official PR: %s\n", manifestPath)
	}

	// If bucket is configured, commit to it
	if cfg.Bucket != "" {
		if err := p.commitToBucket(ctx, cfg.Bucket, data, manifest); err != nil {
			return err
		}
	}

	return nil
}

func (p *ScoopPublisher) commitToBucket(ctx context.Context, bucket string, data scoopTemplateData, manifest string) error {
	tmpDir, err := os.MkdirTemp("", "scoop-bucket-*")
	if err != nil {
		return fmt.Errorf("scoop.Publish: failed to create temp directory: %w", err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	fmt.Printf("Cloning bucket %s...\n", bucket)
	cmd := exec.CommandContext(ctx, "gh", "repo", "clone", bucket, tmpDir, "--", "--depth=1")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("scoop.Publish: failed to clone bucket: %w", err)
	}

	// Ensure bucket directory exists
	bucketDir := filepath.Join(tmpDir, "bucket")
	if _, err := os.Stat(bucketDir); os.IsNotExist(err) {
		bucketDir = tmpDir // Some repos put manifests in root
	}

	manifestPath := filepath.Join(bucketDir, fmt.Sprintf("%s.json", data.PackageName))
	if err := os.WriteFile(manifestPath, []byte(manifest), 0644); err != nil {
		return fmt.Errorf("scoop.Publish: failed to write manifest: %w", err)
	}

	commitMsg := fmt.Sprintf("Update %s to %s", data.PackageName, data.Version)

	cmd = exec.CommandContext(ctx, "git", "add", ".")
	cmd.Dir = tmpDir
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("scoop.Publish: git add failed: %w", err)
	}

	cmd = exec.CommandContext(ctx, "git", "commit", "-m", commitMsg)
	cmd.Dir = tmpDir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("scoop.Publish: git commit failed: %w", err)
	}

	cmd = exec.CommandContext(ctx, "git", "push")
	cmd.Dir = tmpDir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("scoop.Publish: git push failed: %w", err)
	}

	fmt.Printf("Updated Scoop bucket: %s\n", bucket)
	return nil
}

func (p *ScoopPublisher) renderTemplate(m io.Medium, name string, data scoopTemplateData) (string, error) {
	var content []byte
	var err error

	// Try custom template from medium
	customPath := filepath.Join(".core", name)
	if m != nil && m.IsFile(customPath) {
		customContent, err := m.Read(customPath)
		if err == nil {
			content = []byte(customContent)
		}
	}

	// Fallback to embedded template
	if content == nil {
		content, err = scoopTemplates.ReadFile(name)
		if err != nil {
			return "", fmt.Errorf("failed to read template %s: %w", name, err)
		}
	}

	tmpl, err := template.New(filepath.Base(name)).Parse(string(content))
	if err != nil {
		return "", fmt.Errorf("failed to parse template %s: %w", name, err)
	}

	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, data); err != nil {
		return "", fmt.Errorf("failed to execute template %s: %w", name, err)
	}

	return buf.String(), nil
}

// Ensure build package is used
var _ = build.Artifact{}
