package php

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"
	"time"

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

func TestCoolifyClient_Good(t *testing.T) {
	t.Run("creates client with correct base URL", func(t *testing.T) {
		client := NewCoolifyClient("https://coolify.example.com", "token")

		assert.Equal(t, "https://coolify.example.com", client.BaseURL)
		assert.Equal(t, "token", client.Token)
		assert.NotNil(t, client.HTTPClient)
	})

	t.Run("strips trailing slash from base URL", func(t *testing.T) {
		client := NewCoolifyClient("https://coolify.example.com/", "token")
		assert.Equal(t, "https://coolify.example.com", client.BaseURL)
	})

	t.Run("http client has timeout", func(t *testing.T) {
		client := NewCoolifyClient("https://coolify.example.com", "token")
		assert.Equal(t, 30*time.Second, client.HTTPClient.Timeout)
	})
}

func TestCoolifyConfig_Good(t *testing.T) {
	t.Run("all fields accessible", func(t *testing.T) {
		config := CoolifyConfig{
			URL:          "https://coolify.example.com",
			Token:        "secret-token",
			AppID:        "app-123",
			StagingAppID: "staging-456",
		}

		assert.Equal(t, "https://coolify.example.com", config.URL)
		assert.Equal(t, "secret-token", config.Token)
		assert.Equal(t, "app-123", config.AppID)
		assert.Equal(t, "staging-456", config.StagingAppID)
	})
}

func TestCoolifyDeployment_Good(t *testing.T) {
	t.Run("all fields accessible", func(t *testing.T) {
		now := time.Now()
		deployment := CoolifyDeployment{
			ID:          "dep-123",
			Status:      "finished",
			CommitSHA:   "abc123",
			CommitMsg:   "Test commit",
			Branch:      "main",
			CreatedAt:   now,
			FinishedAt:  now.Add(5 * time.Minute),
			Log:         "Build successful",
			DeployedURL: "https://app.example.com",
		}

		assert.Equal(t, "dep-123", deployment.ID)
		assert.Equal(t, "finished", deployment.Status)
		assert.Equal(t, "abc123", deployment.CommitSHA)
		assert.Equal(t, "Test commit", deployment.CommitMsg)
		assert.Equal(t, "main", deployment.Branch)
	})
}

func TestCoolifyApp_Good(t *testing.T) {
	t.Run("all fields accessible", func(t *testing.T) {
		app := CoolifyApp{
			ID:          "app-123",
			Name:        "MyApp",
			FQDN:        "https://myapp.example.com",
			Status:      "running",
			Repository:  "https://github.com/user/repo",
			Branch:      "main",
			Environment: "production",
		}

		assert.Equal(t, "app-123", app.ID)
		assert.Equal(t, "MyApp", app.Name)
		assert.Equal(t, "https://myapp.example.com", app.FQDN)
		assert.Equal(t, "running", app.Status)
	})
}

func TestLoadCoolifyConfigFromFile_Good(t *testing.T) {
	t.Run("loads config from .env file", func(t *testing.T) {
		dir := t.TempDir()
		envContent := `COOLIFY_URL=https://coolify.example.com
COOLIFY_TOKEN=secret-token
COOLIFY_APP_ID=app-123
COOLIFY_STAGING_APP_ID=staging-456`

		err := os.WriteFile(filepath.Join(dir, ".env"), []byte(envContent), 0644)
		require.NoError(t, err)

		config, err := LoadCoolifyConfigFromFile(filepath.Join(dir, ".env"))
		assert.NoError(t, err)
		assert.Equal(t, "https://coolify.example.com", config.URL)
		assert.Equal(t, "secret-token", config.Token)
		assert.Equal(t, "app-123", config.AppID)
		assert.Equal(t, "staging-456", config.StagingAppID)
	})

	t.Run("handles quoted values", func(t *testing.T) {
		dir := t.TempDir()
		envContent := `COOLIFY_URL="https://coolify.example.com"
COOLIFY_TOKEN='secret-token'`

		err := os.WriteFile(filepath.Join(dir, ".env"), []byte(envContent), 0644)
		require.NoError(t, err)

		config, err := LoadCoolifyConfigFromFile(filepath.Join(dir, ".env"))
		assert.NoError(t, err)
		assert.Equal(t, "https://coolify.example.com", config.URL)
		assert.Equal(t, "secret-token", config.Token)
	})

	t.Run("ignores comments", func(t *testing.T) {
		dir := t.TempDir()
		envContent := `# This is a comment
COOLIFY_URL=https://coolify.example.com
# COOLIFY_TOKEN=wrong-token
COOLIFY_TOKEN=correct-token`

		err := os.WriteFile(filepath.Join(dir, ".env"), []byte(envContent), 0644)
		require.NoError(t, err)

		config, err := LoadCoolifyConfigFromFile(filepath.Join(dir, ".env"))
		assert.NoError(t, err)
		assert.Equal(t, "correct-token", config.Token)
	})

	t.Run("ignores blank lines", func(t *testing.T) {
		dir := t.TempDir()
		envContent := `COOLIFY_URL=https://coolify.example.com

COOLIFY_TOKEN=secret-token`

		err := os.WriteFile(filepath.Join(dir, ".env"), []byte(envContent), 0644)
		require.NoError(t, err)

		config, err := LoadCoolifyConfigFromFile(filepath.Join(dir, ".env"))
		assert.NoError(t, err)
		assert.Equal(t, "https://coolify.example.com", config.URL)
	})
}

func TestLoadCoolifyConfigFromFile_Bad(t *testing.T) {
	t.Run("fails when COOLIFY_URL missing", func(t *testing.T) {
		dir := t.TempDir()
		envContent := `COOLIFY_TOKEN=secret-token`

		err := os.WriteFile(filepath.Join(dir, ".env"), []byte(envContent), 0644)
		require.NoError(t, err)

		_, err = LoadCoolifyConfigFromFile(filepath.Join(dir, ".env"))
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "COOLIFY_URL is not set")
	})

	t.Run("fails when COOLIFY_TOKEN missing", func(t *testing.T) {
		dir := t.TempDir()
		envContent := `COOLIFY_URL=https://coolify.example.com`

		err := os.WriteFile(filepath.Join(dir, ".env"), []byte(envContent), 0644)
		require.NoError(t, err)

		_, err = LoadCoolifyConfigFromFile(filepath.Join(dir, ".env"))
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "COOLIFY_TOKEN is not set")
	})
}

func TestLoadCoolifyConfig_FromDirectory_Good(t *testing.T) {
	t.Run("loads from directory", func(t *testing.T) {
		dir := t.TempDir()
		envContent := `COOLIFY_URL=https://coolify.example.com
COOLIFY_TOKEN=secret-token`

		err := os.WriteFile(filepath.Join(dir, ".env"), []byte(envContent), 0644)
		require.NoError(t, err)

		config, err := LoadCoolifyConfig(dir)
		assert.NoError(t, err)
		assert.Equal(t, "https://coolify.example.com", config.URL)
	})
}

func TestValidateCoolifyConfig_Bad(t *testing.T) {
	t.Run("returns error for empty URL", func(t *testing.T) {
		config := &CoolifyConfig{Token: "token"}
		_, err := validateCoolifyConfig(config)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "COOLIFY_URL is not set")
	})

	t.Run("returns error for empty token", func(t *testing.T) {
		config := &CoolifyConfig{URL: "https://coolify.example.com"}
		_, err := validateCoolifyConfig(config)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "COOLIFY_TOKEN is not set")
	})
}

func TestCoolifyClient_TriggerDeploy_Good(t *testing.T) {
	t.Run("triggers deployment successfully", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "/api/v1/applications/app-123/deploy", r.URL.Path)
			assert.Equal(t, "POST", r.Method)
			assert.Equal(t, "Bearer secret-token", r.Header.Get("Authorization"))
			assert.Equal(t, "application/json", r.Header.Get("Content-Type"))

			resp := CoolifyDeployment{
				ID:        "dep-456",
				Status:    "queued",
				CreatedAt: time.Now(),
			}
			json.NewEncoder(w).Encode(resp)
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "secret-token")
		deployment, err := client.TriggerDeploy(context.Background(), "app-123", false)

		assert.NoError(t, err)
		assert.Equal(t, "dep-456", deployment.ID)
		assert.Equal(t, "queued", deployment.Status)
	})

	t.Run("triggers deployment with force", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			var body map[string]interface{}
			json.NewDecoder(r.Body).Decode(&body)
			assert.Equal(t, true, body["force"])

			resp := CoolifyDeployment{ID: "dep-456", Status: "queued"}
			json.NewEncoder(w).Encode(resp)
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "secret-token")
		_, err := client.TriggerDeploy(context.Background(), "app-123", true)
		assert.NoError(t, err)
	})

	t.Run("handles minimal response", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Return an invalid JSON response to trigger the fallback
			w.Write([]byte("not json"))
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "secret-token")
		deployment, err := client.TriggerDeploy(context.Background(), "app-123", false)

		assert.NoError(t, err)
		// The fallback response should be returned
		assert.Equal(t, "queued", deployment.Status)
	})
}

func TestCoolifyClient_TriggerDeploy_Bad(t *testing.T) {
	t.Run("fails on HTTP error", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusInternalServerError)
			json.NewEncoder(w).Encode(map[string]string{"message": "Internal error"})
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "secret-token")
		_, err := client.TriggerDeploy(context.Background(), "app-123", false)

		assert.Error(t, err)
		assert.Contains(t, err.Error(), "API error")
	})
}

func TestCoolifyClient_GetDeployment_Good(t *testing.T) {
	t.Run("gets deployment details", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "/api/v1/applications/app-123/deployments/dep-456", r.URL.Path)
			assert.Equal(t, "GET", r.Method)

			resp := CoolifyDeployment{
				ID:        "dep-456",
				Status:    "finished",
				CommitSHA: "abc123",
				Branch:    "main",
			}
			json.NewEncoder(w).Encode(resp)
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "secret-token")
		deployment, err := client.GetDeployment(context.Background(), "app-123", "dep-456")

		assert.NoError(t, err)
		assert.Equal(t, "dep-456", deployment.ID)
		assert.Equal(t, "finished", deployment.Status)
		assert.Equal(t, "abc123", deployment.CommitSHA)
	})
}

func TestCoolifyClient_GetDeployment_Bad(t *testing.T) {
	t.Run("fails on 404", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusNotFound)
			json.NewEncoder(w).Encode(map[string]string{"error": "Not found"})
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "secret-token")
		_, err := client.GetDeployment(context.Background(), "app-123", "dep-456")

		assert.Error(t, err)
		assert.Contains(t, err.Error(), "Not found")
	})
}

func TestCoolifyClient_ListDeployments_Good(t *testing.T) {
	t.Run("lists deployments", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "/api/v1/applications/app-123/deployments", r.URL.Path)
			assert.Equal(t, "10", r.URL.Query().Get("limit"))

			resp := []CoolifyDeployment{
				{ID: "dep-1", Status: "finished"},
				{ID: "dep-2", Status: "failed"},
			}
			json.NewEncoder(w).Encode(resp)
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "secret-token")
		deployments, err := client.ListDeployments(context.Background(), "app-123", 10)

		assert.NoError(t, err)
		assert.Len(t, deployments, 2)
		assert.Equal(t, "dep-1", deployments[0].ID)
		assert.Equal(t, "dep-2", deployments[1].ID)
	})

	t.Run("lists without limit", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "", r.URL.Query().Get("limit"))
			json.NewEncoder(w).Encode([]CoolifyDeployment{})
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "secret-token")
		_, err := client.ListDeployments(context.Background(), "app-123", 0)
		assert.NoError(t, err)
	})
}

func TestCoolifyClient_Rollback_Good(t *testing.T) {
	t.Run("triggers rollback", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "/api/v1/applications/app-123/rollback", r.URL.Path)
			assert.Equal(t, "POST", r.Method)

			var body map[string]string
			json.NewDecoder(r.Body).Decode(&body)
			assert.Equal(t, "dep-old", body["deployment_id"])

			resp := CoolifyDeployment{
				ID:     "dep-new",
				Status: "rolling_back",
			}
			json.NewEncoder(w).Encode(resp)
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "secret-token")
		deployment, err := client.Rollback(context.Background(), "app-123", "dep-old")

		assert.NoError(t, err)
		assert.Equal(t, "dep-new", deployment.ID)
		assert.Equal(t, "rolling_back", deployment.Status)
	})
}

func TestCoolifyClient_GetApp_Good(t *testing.T) {
	t.Run("gets app details", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "/api/v1/applications/app-123", r.URL.Path)
			assert.Equal(t, "GET", r.Method)

			resp := CoolifyApp{
				ID:     "app-123",
				Name:   "MyApp",
				FQDN:   "https://myapp.example.com",
				Status: "running",
			}
			json.NewEncoder(w).Encode(resp)
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "secret-token")
		app, err := client.GetApp(context.Background(), "app-123")

		assert.NoError(t, err)
		assert.Equal(t, "app-123", app.ID)
		assert.Equal(t, "MyApp", app.Name)
		assert.Equal(t, "https://myapp.example.com", app.FQDN)
	})
}

func TestCoolifyClient_SetHeaders(t *testing.T) {
	t.Run("sets all required headers", func(t *testing.T) {
		client := NewCoolifyClient("https://coolify.example.com", "my-token")
		req, _ := http.NewRequest("GET", "https://coolify.example.com", nil)

		client.setHeaders(req)

		assert.Equal(t, "Bearer my-token", req.Header.Get("Authorization"))
		assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
		assert.Equal(t, "application/json", req.Header.Get("Accept"))
	})
}

func TestCoolifyClient_ParseError(t *testing.T) {
	t.Run("parses message field", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusBadRequest)
			json.NewEncoder(w).Encode(map[string]string{"message": "Bad request message"})
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "token")
		_, err := client.GetApp(context.Background(), "app-123")

		assert.Error(t, err)
		assert.Contains(t, err.Error(), "Bad request message")
	})

	t.Run("parses error field", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusBadRequest)
			json.NewEncoder(w).Encode(map[string]string{"error": "Error message"})
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "token")
		_, err := client.GetApp(context.Background(), "app-123")

		assert.Error(t, err)
		assert.Contains(t, err.Error(), "Error message")
	})

	t.Run("returns raw body when no JSON fields", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("Raw error message"))
		}))
		defer server.Close()

		client := NewCoolifyClient(server.URL, "token")
		_, err := client.GetApp(context.Background(), "app-123")

		assert.Error(t, err)
		assert.Contains(t, err.Error(), "Raw error message")
	})
}

func TestEnvironmentVariablePriority(t *testing.T) {
	t.Run("env vars take precedence over .env file", func(t *testing.T) {
		dir := t.TempDir()
		envContent := `COOLIFY_URL=https://from-file.com
COOLIFY_TOKEN=file-token`

		err := os.WriteFile(filepath.Join(dir, ".env"), []byte(envContent), 0644)
		require.NoError(t, err)

		// Set environment variables
		origURL := os.Getenv("COOLIFY_URL")
		origToken := os.Getenv("COOLIFY_TOKEN")
		defer func() {
			os.Setenv("COOLIFY_URL", origURL)
			os.Setenv("COOLIFY_TOKEN", origToken)
		}()

		os.Setenv("COOLIFY_URL", "https://from-env.com")
		os.Setenv("COOLIFY_TOKEN", "env-token")

		config, err := LoadCoolifyConfig(dir)
		assert.NoError(t, err)
		// Environment variables should take precedence
		assert.Equal(t, "https://from-env.com", config.URL)
		assert.Equal(t, "env-token", config.Token)
	})
}
