package compose

import (
	"context"
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestLoadProject_EnvFileInterpolation tests that environment variables from .env files
// are properly loaded and available for interpolation in compose files.
func TestLoadProject_EnvFileInterpolation(t *testing.T) {
	tests := []struct {
		name        string
		composeYAML string
		envFile     string
		verify      func(t *testing.T, projectDir string)
	}{
		// This is a regression test for https://github.com/psviderski/uncloud/issues/144.
		{
			name: "simple variable interpolation",
			envFile: `KEY1=value1
KEY2=value2
`,
			composeYAML: `services:
  web:
    image: nginx
    command: ["echo", "${KEY1}", "${KEY2}"]
`,
			verify: func(t *testing.T, projectDir string) {
				project, err := LoadProject(context.Background(), []string{filepath.Join(projectDir, "compose.yaml")})
				require.NoError(t, err)
				require.NotNil(t, project)
				require.Len(t, project.Services, 1)

				webService := project.Services["web"]
				require.NotNil(t, webService)
				require.Len(t, webService.Command, 3)
				assert.Equal(t, "echo", webService.Command[0])
				assert.Equal(t, "value1", webService.Command[1], "KEY1 should be interpolated from .env file")
				assert.Equal(t, "value2", webService.Command[2], "KEY2 should be interpolated from .env file")
			},
		},
		{
			name: "environment variable interpolation in service environment",
			envFile: `DATABASE_URL=postgres://localhost:5432/mydb
REDIS_URL=redis://localhost:6379
`,
			composeYAML: `services:
  app:
    image: myapp:latest
    environment:
      - DB_CONNECTION=${DATABASE_URL}
      - CACHE_URL=${REDIS_URL}
`,
			verify: func(t *testing.T, projectDir string) {
				project, err := LoadProject(context.Background(), []string{filepath.Join(projectDir, "compose.yaml")})
				require.NoError(t, err)
				require.NotNil(t, project)
				require.Len(t, project.Services, 1)

				appService := project.Services["app"]
				require.NotNil(t, appService)
				require.NotNil(t, appService.Environment)

				assert.Equal(t, "postgres://localhost:5432/mydb", *appService.Environment["DB_CONNECTION"],
					"DATABASE_URL should be interpolated from .env file")
				assert.Equal(t, "redis://localhost:6379", *appService.Environment["CACHE_URL"],
					"REDIS_URL should be interpolated from .env file")
			},
		},
		{
			name: "variable with default value when not in env file",
			envFile: `EXISTING_VAR=exists
`,
			composeYAML: `services:
  test:
    image: busybox
    environment:
      - VAR1=${EXISTING_VAR}
      - VAR2=${MISSING_VAR:-default_value}
`,
			verify: func(t *testing.T, projectDir string) {
				project, err := LoadProject(context.Background(), []string{filepath.Join(projectDir, "compose.yaml")})
				require.NoError(t, err)
				require.NotNil(t, project)

				testService := project.Services["test"]
				require.NotNil(t, testService)

				assert.Equal(t, "exists", *testService.Environment["VAR1"],
					"EXISTING_VAR should be interpolated from .env file")
				assert.Equal(t, "default_value", *testService.Environment["VAR2"],
					"MISSING_VAR should use default value")
			},
		},
		{
			name: "os environment overrides .env file",
			envFile: `MY_VAR=from_env_file
`,
			composeYAML: `services:
  override:
    image: alpine
    environment:
      - TEST_VAR=${MY_VAR}
`,
			verify: func(t *testing.T, projectDir string) {
				t.Setenv("MY_VAR", "from_os_env")

				project, err := LoadProject(context.Background(), []string{filepath.Join(projectDir, "compose.yaml")})
				require.NoError(t, err)
				require.NotNil(t, project)

				overrideService := project.Services["override"]
				require.NotNil(t, overrideService)

				// OS environment should win over .env file (WithOsEnv comes before WithDotEnv).
				assert.Equal(t, "from_os_env", *overrideService.Environment["TEST_VAR"],
					"OS environment should override .env file")
			},
		},
		{
			name:    "no .env file - missing variables get empty string",
			envFile: "",
			composeYAML: `services:
  missing:
    image: ubuntu
    command: ["echo", "${UNDEFINED_VAR}"]
`,
			verify: func(t *testing.T, projectDir string) {
				os.Remove(filepath.Join(projectDir, ".env"))

				project, err := LoadProject(context.Background(), []string{filepath.Join(projectDir, "compose.yaml")})
				require.NoError(t, err)
				require.NotNil(t, project)

				missingService := project.Services["missing"]
				require.NotNil(t, missingService)
				require.Len(t, missingService.Command, 2)
				assert.Equal(t, "", missingService.Command[1], "undefined variable should be empty string")
			},
		},
		{
			name:    "COMPOSE_DISABLE_ENV_FILE disables .env loading",
			envFile: `MY_VAR=from_env_file`,
			composeYAML: `services:
  web:
    image: nginx
    command: ["echo", "${MY_VAR}"]
`,
			verify: func(t *testing.T, projectDir string) {
				t.Setenv("COMPOSE_DISABLE_ENV_FILE", "true")

				project, err := LoadProject(context.Background(), []string{filepath.Join(projectDir, "compose.yaml")})
				require.NoError(t, err)
				require.NotNil(t, project)

				webService := project.Services["web"]
				require.NotNil(t, webService)
				require.Len(t, webService.Command, 2)

				assert.Equal(t, "", webService.Command[1], "variable should be empty when .env file is disabled")
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tempDir := t.TempDir()

			composeFile := filepath.Join(tempDir, "compose.yaml")
			err := os.WriteFile(composeFile, []byte(tt.composeYAML), 0o644)
			require.NoError(t, err)

			// Write .env file if provided.
			if tt.envFile != "" {
				envFile := filepath.Join(tempDir, ".env")
				err = os.WriteFile(envFile, []byte(tt.envFile), 0o644)
				require.NoError(t, err)
			}

			tt.verify(t, tempDir)
		})
	}
}

// TestLoadProject_Unsupported checks that unsupported features lead to warnings.
func TestLoadProject_Unsupported(t *testing.T) {
	// captureStderr runs fn while capturing stderr output and returns what was written.
	captureStderr := func(t *testing.T, fn func()) string {
		t.Helper()
		old := os.Stderr
		r, w, err := os.Pipe()
		require.NoError(t, err)
		os.Stderr = w
		defer func() { os.Stderr = old }()

		fn()

		w.Close()
		out, err := io.ReadAll(r)
		require.NoError(t, err)
		r.Close()
		return string(out)
	}

	tests := []struct {
		name         string
		composeYAML  string
		warnCount    int
		warnContains []string
	}{
		{
			name: "unsupported dns",
			composeYAML: `services:
  app:
    image: myapp:latest
    dns: 8.8.8.8
`,
			warnCount:    1,
			warnContains: []string{"dns"},
		},
		{
			name: "unsupported networks",
			composeYAML: `services:
  app:
    image: myapp:latest
    networks:
      - frontend

networks:
  frontend:
`,
			warnCount:    1,
			warnContains: []string{"networks"},
		},
		{
			name: "unsupported depends_on service_completed_successfully",
			composeYAML: `services:
  migrate:
    image: alpine
    command: ["true"]
  app:
    image: nginx
    depends_on:
      migrate:
        condition: service_completed_successfully
`,
			warnCount: 1,
			warnContains: []string{
				"service_completed_successfully",
				"pre-deploy hook",
				"https://uncloud.run/docs/guides/deployments/pre-deploy-hooks",
			},
		},
		{
			name: "multiple unsupported features",
			composeYAML: `services:
  app:
    image: myapp:latest
    dns: 8.8.8.8
    links:
      - db
  db:
    image: postgres:latest
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secret.txt
`,
			warnCount:    3,
			warnContains: []string{"dns", "links", "secrets"},
		},
		{
			name: "supported networks",
			composeYAML: `services:
  app:
    image: myapp:latest
    networks:
      default: {}

  web:
    image: nginx
    networks:
      - default

networks:
  default:
`,
			warnCount: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			stderr := captureStderr(t, func() {
				_, err := LoadProjectFromContent(context.Background(), tt.composeYAML)
				require.NoError(t, err)
			})

			if tt.warnCount == 0 {
				assert.Empty(t, stderr)
			} else {
				assert.Equal(t, tt.warnCount, strings.Count(stderr, "WARNING:"),
					"expected %d warnings, got stderr: %s", tt.warnCount, stderr)
				for _, substr := range tt.warnContains {
					assert.Contains(t, stderr, substr)
				}
			}
		})
	}
}
