package helper

import (
	"bytes"
	"io"
	"log"
	"os"
	"strings"
	"testing"
)

func captureLogOutput(f func()) string {
	var buf bytes.Buffer
	log.SetOutput(&buf)
	defer log.SetOutput(os.Stderr)
	f()
	return buf.String()
}

func TestLogInfo_Development(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	output := captureLogOutput(func() {
		LogInfo("Test info message")
	})

	if !strings.Contains(output, "INFO:") {
		t.Error("Expected INFO prefix in log output")
	}

	if !strings.Contains(output, "Test info message") {
		t.Error("Expected message to be logged")
	}
}

func TestLogInfo_Debug(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "debug")

	output := captureLogOutput(func() {
		LogInfo("Debug info message")
	})

	if !strings.Contains(output, "INFO:") {
		t.Error("Expected INFO prefix in log output")
	}

	if !strings.Contains(output, "Debug info message") {
		t.Error("Expected message to be logged")
	}
}

func TestLogInfo_Production(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "production")

	output := captureLogOutput(func() {
		LogInfo("Production info message")
	})

	if !strings.Contains(output, "INFO:") {
		t.Error("Expected INFO prefix in log output")
	}

	if !strings.Contains(output, "Production info message") {
		t.Error("Expected message to be logged")
	}
}

func TestLogInfo_NoEnv(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Unsetenv("GO_ENV")

	// LogInfo calls log.Fatal if GO_ENV not set, which exits the process
	// We can't easily test this without subprocess, so we'll skip this specific case
	// or test that it panics/exits
	t.Skip("Cannot test log.Fatal without subprocess")
}

func TestLogWarn_Development(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	output := captureLogOutput(func() {
		LogWarn("Test warning")
	})

	if !strings.Contains(output, "WARNING:") {
		t.Error("Expected WARNING prefix in log output")
	}

	if !strings.Contains(output, "Test warning") {
		t.Error("Expected warning message to be logged")
	}
}

func TestLogWarn_Debug(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "debug")

	output := captureLogOutput(func() {
		LogWarn("Debug warning")
	})

	if !strings.Contains(output, "WARNING:") {
		t.Error("Expected WARNING prefix in log output")
	}
}

func TestLogError_Development(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	testErr := &testError{"test error"}

	output := captureLogOutput(func() {
		LogError(testErr, "Error occurred")
	})

	if !strings.Contains(output, "ERROR:") {
		t.Error("Expected ERROR prefix in log output")
	}

	if !strings.Contains(output, "Error occurred") {
		t.Error("Expected error message to be logged")
	}

	if !strings.Contains(output, "test error") {
		t.Error("Expected error details to be logged")
	}
}

func TestLogError_NilError(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	output := captureLogOutput(func() {
		LogError(nil, "Error message only")
	})

	if !strings.Contains(output, "ERROR:") {
		t.Error("Expected ERROR prefix")
	}

	if !strings.Contains(output, "Error message only") {
		t.Error("Expected message to be logged")
	}
}

func TestLogError_Debug(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "debug")

	testErr := &testError{"debug error"}

	output := captureLogOutput(func() {
		LogError(testErr, "Debug error occurred")
	})

	if !strings.Contains(output, "ERROR:") {
		t.Error("Expected ERROR prefix")
	}

	if !strings.Contains(output, "Debug error occurred") {
		t.Error("Expected error message")
	}
}

type testError struct {
	msg string
}

func (e *testError) Error() string {
	return e.msg
}

func TestLogInfo_EmptyMessage(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	output := captureLogOutput(func() {
		LogInfo("")
	})

	if !strings.Contains(output, "INFO:") {
		t.Error("Expected INFO prefix even with empty message")
	}
}

func TestLogWarn_EmptyMessage(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	output := captureLogOutput(func() {
		LogWarn("")
	})

	if !strings.Contains(output, "WARNING:") {
		t.Error("Expected WARNING prefix even with empty message")
	}
}

func TestLogError_EmptyMessage(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	output := captureLogOutput(func() {
		LogError(nil, "")
	})

	if !strings.Contains(output, "ERROR:") {
		t.Error("Expected ERROR prefix even with empty message")
	}
}

func TestLogInfo_LongMessage(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	longMessage := strings.Repeat("A", 1000)

	output := captureLogOutput(func() {
		LogInfo(longMessage)
	})

	if !strings.Contains(output, longMessage) {
		t.Error("Expected long message to be logged completely")
	}
}

func TestLogWarn_SpecialCharacters(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	specialMsg := "Warning: \n\t special characters & symbols!"

	output := captureLogOutput(func() {
		LogWarn(specialMsg)
	})

	if !strings.Contains(output, "WARNING:") {
		t.Error("Expected WARNING prefix")
	}
}

func TestLogError_MultilineMessage(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	multilineMsg := "Line 1\nLine 2\nLine 3"

	output := captureLogOutput(func() {
		LogError(nil, multilineMsg)
	})

	if !strings.Contains(output, "ERROR:") {
		t.Error("Expected ERROR prefix")
	}
}

func TestLogInfo_Canary(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "canary")

	output := captureLogOutput(func() {
		LogInfo("Canary info message")
	})

	if !strings.Contains(output, "INFO:") {
		t.Error("Expected INFO prefix in canary environment")
	}

	if !strings.Contains(output, "Canary info message") {
		t.Error("Expected message to be logged in canary environment")
	}
}

func TestLogEnvironmentCheck(t *testing.T) {
	validEnvironments := []string{"development", "debug", "production", "canary"}

	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	for _, env := range validEnvironments {
		t.Run(env, func(t *testing.T) {
			os.Setenv("GO_ENV", env)

			output := captureLogOutput(func() {
				LogInfo("Test message")
			})

			if output == "" {
				t.Errorf("Expected log output for environment: %s", env)
			}
		})
	}
}

func TestLogError_WithAndWithoutError(t *testing.T) {
	originalEnv := os.Getenv("GO_ENV")
	defer os.Setenv("GO_ENV", originalEnv)

	os.Setenv("GO_ENV", "development")

	// With error
	output1 := captureLogOutput(func() {
		LogError(&testError{"actual error"}, "Context message")
	})

	if !strings.Contains(output1, "actual error") {
		t.Error("Expected error details when error provided")
	}

	// Without error
	output2 := captureLogOutput(func() {
		LogError(nil, "Context message")
	})

	if strings.Contains(output2, "<nil>") {
		t.Log("nil error is logged as <nil>")
	}
}

func BenchmarkLogInfo(b *testing.B) {
	os.Setenv("GO_ENV", "development")
	defer os.Unsetenv("GO_ENV")

	// Discard log output for benchmarking
	log.SetOutput(io.Discard)
	defer log.SetOutput(os.Stderr)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		LogInfo("Benchmark message")
	}
}

func BenchmarkLogWarn(b *testing.B) {
	os.Setenv("GO_ENV", "development")
	defer os.Unsetenv("GO_ENV")

	log.SetOutput(io.Discard)
	defer log.SetOutput(os.Stderr)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		LogWarn("Benchmark warning")
	}
}

func BenchmarkLogError(b *testing.B) {
	os.Setenv("GO_ENV", "development")
	defer os.Unsetenv("GO_ENV")

	log.SetOutput(io.Discard)
	defer log.SetOutput(os.Stderr)

	testErr := &testError{"benchmark error"}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		LogError(testErr, "Benchmark error message")
	}
}
