// ABOUTME: Unit tests for App management APIs using httptest mock server
// ABOUTME: Tests create, show, list, and delete operations with error conditions

package v2

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

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

func TestCreateApp(t *testing.T) {
	tests := []struct {
		name           string
		input          *NewAppInput
		mockStatusCode int
		mockResponse   string
		expectError    bool
	}{
		{
			name: "successful creation",
			input: &NewAppInput{
				Region: "us-west",
				App: App{
					Key: AppKey{
						Organization: "testorg",
						Name:         "testapp",
						Version:      "1.0.0",
					},
					Deployment: "kubernetes",
				},
			},
			mockStatusCode: 200,
			mockResponse:   `{"message": "success"}`,
			expectError:    false,
		},
		{
			name: "validation error",
			input: &NewAppInput{
				Region: "us-west",
				App: App{
					Key: AppKey{
						Organization: "",
						Name:         "testapp",
						Version:      "1.0.0",
					},
				},
			},
			mockStatusCode: 400,
			mockResponse:   `{"message": "organization is required"}`,
			expectError:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mock server
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				assert.Equal(t, "POST", r.Method)
				assert.Equal(t, "/api/v1/auth/ctrl/CreateApp", r.URL.Path)
				assert.Equal(t, "application/json", r.Header.Get("Content-Type"))

				w.WriteHeader(tt.mockStatusCode)
				_, _ = w.Write([]byte(tt.mockResponse))
			}))
			defer server.Close()

			// Create client
			client := NewClient(server.URL,
				WithHTTPClient(&http.Client{Timeout: 5 * time.Second}),
				WithAuthProvider(NewStaticTokenProvider("test-token")),
			)

			// Execute test
			ctx := context.Background()
			err := client.CreateApp(ctx, tt.input)

			// Verify results
			if tt.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestShowApp(t *testing.T) {
	tests := []struct {
		name           string
		appKey         AppKey
		region         string
		mockStatusCode int
		mockResponse   string
		expectError    bool
		expectNotFound bool
	}{
		{
			name: "successful show",
			appKey: AppKey{
				Organization: "testorg",
				Name:         "testapp",
				Version:      "1.0.0",
			},
			region:         "us-west",
			mockStatusCode: 200,
			mockResponse: `{"data": {"key": {"organization": "testorg", "name": "testapp", "version": "1.0.0"}, "deployment": "kubernetes"}}
`,
			expectError:    false,
			expectNotFound: false,
		},
		{
			name: "app not found",
			appKey: AppKey{
				Organization: "testorg",
				Name:         "nonexistent",
				Version:      "1.0.0",
			},
			region:         "us-west",
			mockStatusCode: 404,
			mockResponse:   "",
			expectError:    true,
			expectNotFound: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mock server
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				assert.Equal(t, "POST", r.Method)
				assert.Equal(t, "/api/v1/auth/ctrl/ShowApp", r.URL.Path)

				w.WriteHeader(tt.mockStatusCode)
				if tt.mockResponse != "" {
					_, _ = w.Write([]byte(tt.mockResponse))
				}
			}))
			defer server.Close()

			// Create client
			client := NewClient(server.URL,
				WithHTTPClient(&http.Client{Timeout: 5 * time.Second}),
			)

			// Execute test
			ctx := context.Background()
			app, err := client.ShowApp(ctx, tt.appKey, tt.region)

			// Verify results
			if tt.expectError {
				assert.Error(t, err)
				if tt.expectNotFound {
					assert.Contains(t, err.Error(), "resource not found")
				}
			} else {
				require.NoError(t, err)
				assert.Equal(t, tt.appKey.Organization, app.Key.Organization)
				assert.Equal(t, tt.appKey.Name, app.Key.Name)
				assert.Equal(t, tt.appKey.Version, app.Key.Version)
			}
		})
	}
}

func TestShowApps(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, "POST", r.Method)
		assert.Equal(t, "/api/v1/auth/ctrl/ShowApp", r.URL.Path)

		// Verify request body
		var filter AppFilter
		err := json.NewDecoder(r.Body).Decode(&filter)
		require.NoError(t, err)
		assert.Equal(t, "testorg", filter.App.Key.Organization)
		assert.Equal(t, "us-west", filter.Region)

		// Return multiple apps
		response := `{"data": {"key": {"organization": "testorg", "name": "app1", "version": "1.0.0"}, "deployment": "kubernetes"}}
{"data": {"key": {"organization": "testorg", "name": "app2", "version": "1.0.0"}, "deployment": "docker"}}
`
		w.WriteHeader(200)
		_, _ = w.Write([]byte(response))
	}))
	defer server.Close()

	client := NewClient(server.URL)
	ctx := context.Background()

	apps, err := client.ShowApps(ctx, AppKey{Organization: "testorg"}, "us-west")

	require.NoError(t, err)
	assert.Len(t, apps, 2)
	assert.Equal(t, "app1", apps[0].Key.Name)
	assert.Equal(t, "app2", apps[1].Key.Name)
}

func TestUpdateApp(t *testing.T) {
	tests := []struct {
		name           string
		input          *UpdateAppInput
		mockStatusCode int
		mockResponse   string
		expectError    bool
	}{
		{
			name: "successful update",
			input: &UpdateAppInput{
				Region: "us-west",
				App: App{
					Key: AppKey{
						Organization: "testorg",
						Name:         "testapp",
						Version:      "1.0.0",
					},
					Deployment: "kubernetes",
					ImagePath:  "nginx:latest",
				},
			},
			mockStatusCode: 200,
			mockResponse:   `{"message": "success"}`,
			expectError:    false,
		},
		{
			name: "validation error",
			input: &UpdateAppInput{
				Region: "us-west",
				App: App{
					Key: AppKey{
						Organization: "",
						Name:         "testapp",
						Version:      "1.0.0",
					},
				},
			},
			mockStatusCode: 400,
			mockResponse:   `{"message": "organization is required"}`,
			expectError:    true,
		},
		{
			name: "app not found",
			input: &UpdateAppInput{
				Region: "us-west",
				App: App{
					Key: AppKey{
						Organization: "testorg",
						Name:         "nonexistent",
						Version:      "1.0.0",
					},
				},
			},
			mockStatusCode: 404,
			mockResponse:   `{"message": "app not found"}`,
			expectError:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mock server
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				assert.Equal(t, "POST", r.Method)
				assert.Equal(t, "/api/v1/auth/ctrl/UpdateApp", r.URL.Path)
				assert.Equal(t, "application/json", r.Header.Get("Content-Type"))

				// Verify request body
				var input UpdateAppInput
				err := json.NewDecoder(r.Body).Decode(&input)
				require.NoError(t, err)
				assert.Equal(t, tt.input.Region, input.Region)
				assert.Equal(t, tt.input.App.Key.Organization, input.App.Key.Organization)

				w.WriteHeader(tt.mockStatusCode)
				_, _ = w.Write([]byte(tt.mockResponse))
			}))
			defer server.Close()

			// Create client
			client := NewClient(server.URL,
				WithHTTPClient(&http.Client{Timeout: 5 * time.Second}),
				WithAuthProvider(NewStaticTokenProvider("test-token")),
			)

			// Execute test
			ctx := context.Background()
			err := client.UpdateApp(ctx, tt.input)

			// Verify results
			if tt.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestDeleteApp(t *testing.T) {
	tests := []struct {
		name           string
		appKey         AppKey
		region         string
		mockStatusCode int
		expectError    bool
	}{
		{
			name: "successful deletion",
			appKey: AppKey{
				Organization: "testorg",
				Name:         "testapp",
				Version:      "1.0.0",
			},
			region:         "us-west",
			mockStatusCode: 200,
			expectError:    false,
		},
		{
			name: "already deleted (404 ok)",
			appKey: AppKey{
				Organization: "testorg",
				Name:         "testapp",
				Version:      "1.0.0",
			},
			region:         "us-west",
			mockStatusCode: 404,
			expectError:    false,
		},
		{
			name: "server error",
			appKey: AppKey{
				Organization: "testorg",
				Name:         "testapp",
				Version:      "1.0.0",
			},
			region:         "us-west",
			mockStatusCode: 500,
			expectError:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				assert.Equal(t, "POST", r.Method)
				assert.Equal(t, "/api/v1/auth/ctrl/DeleteApp", r.URL.Path)

				w.WriteHeader(tt.mockStatusCode)
			}))
			defer server.Close()

			client := NewClient(server.URL)
			ctx := context.Background()

			err := client.DeleteApp(ctx, tt.appKey, tt.region)

			if tt.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestClientOptions(t *testing.T) {
	t.Run("with auth provider", func(t *testing.T) {
		authProvider := NewStaticTokenProvider("test-token")
		client := NewClient("https://example.com",
			WithAuthProvider(authProvider),
		)

		assert.Equal(t, authProvider, client.AuthProvider)
	})

	t.Run("with custom HTTP client", func(t *testing.T) {
		httpClient := &http.Client{Timeout: 10 * time.Second}
		client := NewClient("https://example.com",
			WithHTTPClient(httpClient),
		)

		assert.Equal(t, httpClient, client.HTTPClient)
	})

	t.Run("with retry options", func(t *testing.T) {
		retryOpts := RetryOptions{MaxRetries: 5}
		client := NewClient("https://example.com",
			WithRetryOptions(retryOpts),
		)

		assert.Equal(t, 5, client.RetryOpts.MaxRetries)
	})
}

func TestAPIError(t *testing.T) {
	err := &APIError{
		StatusCode: 400,
		Messages:   []string{"validation failed", "name is required"},
	}

	assert.Contains(t, err.Error(), "validation failed")
	assert.Equal(t, 400, err.StatusCode)
	assert.Len(t, err.Messages, 2)
}
