package publishers

import (
	"bytes"
	"os"
	"testing"

	"github.com/host-uk/core/pkg/build"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestHomebrewPublisher_Name_Good(t *testing.T) {
	t.Run("returns homebrew", func(t *testing.T) {
		p := NewHomebrewPublisher()
		assert.Equal(t, "homebrew", p.Name())
	})
}

func TestHomebrewPublisher_ParseConfig_Good(t *testing.T) {
	p := NewHomebrewPublisher()

	t.Run("uses defaults when no extended config", func(t *testing.T) {
		pubCfg := PublisherConfig{Type: "homebrew"}
		relCfg := &mockReleaseConfig{repository: "owner/repo"}
		cfg := p.parseConfig(pubCfg, relCfg)

		assert.Empty(t, cfg.Tap)
		assert.Empty(t, cfg.Formula)
		assert.Nil(t, cfg.Official)
	})

	t.Run("parses tap and formula from extended config", func(t *testing.T) {
		pubCfg := PublisherConfig{
			Type: "homebrew",
			Extended: map[string]any{
				"tap":     "host-uk/homebrew-tap",
				"formula": "myformula",
			},
		}
		relCfg := &mockReleaseConfig{repository: "owner/repo"}
		cfg := p.parseConfig(pubCfg, relCfg)

		assert.Equal(t, "host-uk/homebrew-tap", cfg.Tap)
		assert.Equal(t, "myformula", cfg.Formula)
	})

	t.Run("parses official config", func(t *testing.T) {
		pubCfg := PublisherConfig{
			Type: "homebrew",
			Extended: map[string]any{
				"official": map[string]any{
					"enabled": true,
					"output":  "dist/brew",
				},
			},
		}
		relCfg := &mockReleaseConfig{repository: "owner/repo"}
		cfg := p.parseConfig(pubCfg, relCfg)

		require.NotNil(t, cfg.Official)
		assert.True(t, cfg.Official.Enabled)
		assert.Equal(t, "dist/brew", cfg.Official.Output)
	})

	t.Run("handles missing official fields", func(t *testing.T) {
		pubCfg := PublisherConfig{
			Type: "homebrew",
			Extended: map[string]any{
				"official": map[string]any{},
			},
		}
		relCfg := &mockReleaseConfig{repository: "owner/repo"}
		cfg := p.parseConfig(pubCfg, relCfg)

		require.NotNil(t, cfg.Official)
		assert.False(t, cfg.Official.Enabled)
		assert.Empty(t, cfg.Official.Output)
	})
}

func TestHomebrewPublisher_ToFormulaClass_Good(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "simple name",
			input:    "core",
			expected: "Core",
		},
		{
			name:     "kebab case",
			input:    "my-cli-tool",
			expected: "MyCliTool",
		},
		{
			name:     "already capitalised",
			input:    "CLI",
			expected: "CLI",
		},
		{
			name:     "single letter",
			input:    "x",
			expected: "X",
		},
		{
			name:     "multiple dashes",
			input:    "my-super-cool-app",
			expected: "MySuperCoolApp",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := toFormulaClass(tc.input)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestHomebrewPublisher_BuildChecksumMap_Good(t *testing.T) {
	t.Run("maps artifacts to checksums by platform", func(t *testing.T) {
		artifacts := []build.Artifact{
			{Path: "/dist/myapp-darwin-amd64.tar.gz", OS: "darwin", Arch: "amd64", Checksum: "abc123"},
			{Path: "/dist/myapp-darwin-arm64.tar.gz", OS: "darwin", Arch: "arm64", Checksum: "def456"},
			{Path: "/dist/myapp-linux-amd64.tar.gz", OS: "linux", Arch: "amd64", Checksum: "ghi789"},
			{Path: "/dist/myapp-linux-arm64.tar.gz", OS: "linux", Arch: "arm64", Checksum: "jkl012"},
			{Path: "/dist/myapp-windows-amd64.zip", OS: "windows", Arch: "amd64", Checksum: "mno345"},
			{Path: "/dist/myapp-windows-arm64.zip", OS: "windows", Arch: "arm64", Checksum: "pqr678"},
		}

		checksums := buildChecksumMap(artifacts)

		assert.Equal(t, "abc123", checksums.DarwinAmd64)
		assert.Equal(t, "def456", checksums.DarwinArm64)
		assert.Equal(t, "ghi789", checksums.LinuxAmd64)
		assert.Equal(t, "jkl012", checksums.LinuxArm64)
		assert.Equal(t, "mno345", checksums.WindowsAmd64)
		assert.Equal(t, "pqr678", checksums.WindowsArm64)
	})

	t.Run("handles empty artifacts", func(t *testing.T) {
		checksums := buildChecksumMap([]build.Artifact{})

		assert.Empty(t, checksums.DarwinAmd64)
		assert.Empty(t, checksums.DarwinArm64)
		assert.Empty(t, checksums.LinuxAmd64)
		assert.Empty(t, checksums.LinuxArm64)
	})

	t.Run("handles partial platform coverage", func(t *testing.T) {
		artifacts := []build.Artifact{
			{Path: "/dist/myapp-darwin-arm64.tar.gz", Checksum: "def456"},
			{Path: "/dist/myapp-linux-amd64.tar.gz", Checksum: "ghi789"},
		}

		checksums := buildChecksumMap(artifacts)

		assert.Empty(t, checksums.DarwinAmd64)
		assert.Equal(t, "def456", checksums.DarwinArm64)
		assert.Equal(t, "ghi789", checksums.LinuxAmd64)
		assert.Empty(t, checksums.LinuxArm64)
	})
}

func TestHomebrewPublisher_RenderTemplate_Good(t *testing.T) {
	p := NewHomebrewPublisher()

	t.Run("renders formula template with data", func(t *testing.T) {
		data := homebrewTemplateData{
			FormulaClass: "MyApp",
			Description:  "My awesome CLI",
			Repository:   "owner/myapp",
			Version:      "1.2.3",
			License:      "MIT",
			BinaryName:   "myapp",
			Checksums: ChecksumMap{
				DarwinAmd64: "abc123",
				DarwinArm64: "def456",
				LinuxAmd64:  "ghi789",
				LinuxArm64:  "jkl012",
			},
		}

		result, err := p.renderTemplate("templates/homebrew/formula.rb.tmpl", data)
		require.NoError(t, err)

		assert.Contains(t, result, "class MyApp < Formula")
		assert.Contains(t, result, `desc "My awesome CLI"`)
		assert.Contains(t, result, `version "1.2.3"`)
		assert.Contains(t, result, `license "MIT"`)
		assert.Contains(t, result, "owner/myapp")
		assert.Contains(t, result, "abc123")
		assert.Contains(t, result, "def456")
		assert.Contains(t, result, "ghi789")
		assert.Contains(t, result, "jkl012")
		assert.Contains(t, result, `bin.install "myapp"`)
	})
}

func TestHomebrewPublisher_RenderTemplate_Bad(t *testing.T) {
	p := NewHomebrewPublisher()

	t.Run("returns error for non-existent template", func(t *testing.T) {
		data := homebrewTemplateData{}
		_, err := p.renderTemplate("templates/homebrew/nonexistent.tmpl", data)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "failed to read template")
	})
}

func TestHomebrewPublisher_DryRunPublish_Good(t *testing.T) {
	p := NewHomebrewPublisher()

	t.Run("outputs expected dry run information", func(t *testing.T) {
		// Capture stdout
		oldStdout := os.Stdout
		r, w, _ := os.Pipe()
		os.Stdout = w

		data := homebrewTemplateData{
			FormulaClass: "MyApp",
			Description:  "My CLI",
			Repository:   "owner/repo",
			Version:      "1.0.0",
			License:      "MIT",
			BinaryName:   "myapp",
			Checksums:    ChecksumMap{},
		}
		cfg := HomebrewConfig{
			Tap: "owner/homebrew-tap",
		}

		err := p.dryRunPublish(data, cfg)

		w.Close()
		var buf bytes.Buffer
		_, _ = buf.ReadFrom(r)
		os.Stdout = oldStdout

		require.NoError(t, err)
		output := buf.String()

		assert.Contains(t, output, "DRY RUN: Homebrew Publish")
		assert.Contains(t, output, "Formula:    MyApp")
		assert.Contains(t, output, "Version:    1.0.0")
		assert.Contains(t, output, "Tap:        owner/homebrew-tap")
		assert.Contains(t, output, "Repository: owner/repo")
		assert.Contains(t, output, "Would commit to tap: owner/homebrew-tap")
		assert.Contains(t, output, "END DRY RUN")
	})

	t.Run("shows official output path when enabled", func(t *testing.T) {
		oldStdout := os.Stdout
		r, w, _ := os.Pipe()
		os.Stdout = w

		data := homebrewTemplateData{
			FormulaClass: "MyApp",
			Version:      "1.0.0",
			BinaryName:   "myapp",
			Checksums:    ChecksumMap{},
		}
		cfg := HomebrewConfig{
			Official: &OfficialConfig{
				Enabled: true,
				Output:  "custom/path",
			},
		}

		err := p.dryRunPublish(data, cfg)

		w.Close()
		var buf bytes.Buffer
		_, _ = buf.ReadFrom(r)
		os.Stdout = oldStdout

		require.NoError(t, err)
		output := buf.String()
		assert.Contains(t, output, "Would write files for official PR to: custom/path")
	})

	t.Run("uses default official output path when not specified", func(t *testing.T) {
		oldStdout := os.Stdout
		r, w, _ := os.Pipe()
		os.Stdout = w

		data := homebrewTemplateData{
			FormulaClass: "MyApp",
			Version:      "1.0.0",
			BinaryName:   "myapp",
			Checksums:    ChecksumMap{},
		}
		cfg := HomebrewConfig{
			Official: &OfficialConfig{
				Enabled: true,
			},
		}

		err := p.dryRunPublish(data, cfg)

		w.Close()
		var buf bytes.Buffer
		_, _ = buf.ReadFrom(r)
		os.Stdout = oldStdout

		require.NoError(t, err)
		output := buf.String()
		assert.Contains(t, output, "Would write files for official PR to: dist/homebrew")
	})
}

func TestHomebrewPublisher_Publish_Bad(t *testing.T) {
	p := NewHomebrewPublisher()

	t.Run("fails when tap not configured and not official mode", func(t *testing.T) {
		release := &Release{
			Version:    "v1.0.0",
			ProjectDir: "/project",
		}
		pubCfg := PublisherConfig{Type: "homebrew"}
		relCfg := &mockReleaseConfig{repository: "owner/repo"}

		err := p.Publish(nil, release, pubCfg, relCfg, false)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "tap is required")
	})
}

func TestHomebrewConfig_Defaults_Good(t *testing.T) {
	t.Run("has sensible defaults", func(t *testing.T) {
		p := NewHomebrewPublisher()
		pubCfg := PublisherConfig{Type: "homebrew"}
		relCfg := &mockReleaseConfig{repository: "owner/repo"}

		cfg := p.parseConfig(pubCfg, relCfg)

		assert.Empty(t, cfg.Tap)
		assert.Empty(t, cfg.Formula)
		assert.Nil(t, cfg.Official)
	})
}
