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

package edgeconnect

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

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

func TestCreateAppInstance(t *testing.T) {
	tests := []struct {
		name           string
		input          *NewAppInstanceInput
		mockStatusCode int
		mockResponse   string
		expectError    bool
		errorContains  string
	}{
		{
			name: "successful creation",
			input: &NewAppInstanceInput{
				Region: "us-west",
				AppInst: AppInstance{
					Key: AppInstanceKey{
						Organization: "testorg",
						Name:         "testinst",
						CloudletKey: CloudletKey{
							Organization: "cloudletorg",
							Name:         "testcloudlet",
						},
					},
					AppKey: AppKey{
						Organization: "testorg",
						Name:         "testapp",
						Version:      "1.0.0",
					},
					Flavor: Flavor{Name: "m4.small"},
				},
			},
			mockStatusCode: 200,
			mockResponse:   `{"message": "success"}`,
			expectError:    false,
		},
		{
			name: "validation error",
			input: &NewAppInstanceInput{
				Region: "us-west",
				AppInst: AppInstance{
					Key: AppInstanceKey{
						Organization: "",
						Name:         "testinst",
					},
				},
			},
			mockStatusCode: 400,
			mockResponse:   `{"message": "organization is required"}`,
			expectError:    true,
		},
		{
			name: "HTTP 200 with CreateError message",
			input: &NewAppInstanceInput{
				Region: "us-west",
				AppInst: AppInstance{
					Key: AppInstanceKey{
						Organization: "testorg",
						Name:         "testinst",
						CloudletKey: CloudletKey{
							Organization: "cloudletorg",
							Name:         "testcloudlet",
						},
					},
					Flavor: Flavor{Name: "m4.small"},
				},
			},
			mockStatusCode: 200,
			mockResponse: `{"data":{"message":"Creating"}}
{"data":{"message":"a service has been configured"}}
{"data":{"message":"CreateError"}}
{"data":{"message":"Deleting AppInst due to failure"}}
{"data":{"message":"Deleted AppInst successfully"}}`,
			expectError:   true,
			errorContains: "CreateError",
		},
		{
			name: "HTTP 200 with result error code",
			input: &NewAppInstanceInput{
				Region: "us-west",
				AppInst: AppInstance{
					Key: AppInstanceKey{
						Organization: "testorg",
						Name:         "testinst",
						CloudletKey: CloudletKey{
							Organization: "cloudletorg",
							Name:         "testcloudlet",
						},
					},
					Flavor: Flavor{Name: "m4.small"},
				},
			},
			mockStatusCode: 200,
			mockResponse: `{"data":{"message":"Creating"}}
{"data":{"message":"a service has been configured"}}
{"data":{"message":"CreateError"}}
{"data":{"message":"Deleting AppInst due to failure"}}
{"data":{"message":"Deleted AppInst successfully"}}
{"result":{"message":"Encountered failures: Create App Inst failed: deployments.apps is forbidden: User \"system:serviceaccount:edgexr:crm-telekomop-munich\" cannot create resource \"deployments\" in API group \"apps\" in the namespace \"gitea\"","code":400}}`,
			expectError:   true,
			errorContains: "deployments.apps is forbidden",
		},
	}

	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/CreateAppInst", 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.CreateAppInstance(ctx, tt.input)

			// Verify results
			if tt.expectError {
				assert.Error(t, err)
				if tt.errorContains != "" {
					assert.Contains(t, err.Error(), tt.errorContains)
				}
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestShowAppInstance(t *testing.T) {
	tests := []struct {
		name           string
		appKey         AppKey
		appInstKey     AppInstanceKey
		region         string
		mockStatusCode int
		mockResponse   string
		expectError    bool
		expectNotFound bool
	}{
		{
			name: "successful show",
			appInstKey: AppInstanceKey{
				Organization: "testorg",
				Name:         "testinst",
				CloudletKey: CloudletKey{
					Organization: "cloudletorg",
					Name:         "testcloudlet",
				},
			},
			appKey:         AppKey{Name: "test-app-id"},
			region:         "us-west",
			mockStatusCode: 200,
			mockResponse: `{"data": {"key": {"organization": "testorg", "name": "testinst", "cloudlet_key": {"organization": "cloudletorg", "name": "testcloudlet"}}, "state": "Ready"}}
`,
			expectError:    false,
			expectNotFound: false,
		},
		{
			name: "instance not found",
			appInstKey: AppInstanceKey{
				Organization: "testorg",
				Name:         "nonexistent",
				CloudletKey: CloudletKey{
					Organization: "cloudletorg",
					Name:         "testcloudlet",
				},
			},
			appKey:         AppKey{Name: "test-app-id"},
			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/ShowAppInst", 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()
			appInst, err := client.ShowAppInstance(ctx, tt.appInstKey, 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.appInstKey.Organization, appInst.Key.Organization)
				assert.Equal(t, tt.appInstKey.Name, appInst.Key.Name)
				assert.Equal(t, "Ready", appInst.State)
			}
		})
	}
}

func TestShowAppInstances(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/ShowAppInst", r.URL.Path)

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

		// Return multiple app instances
		response := `{"data": {"key": {"organization": "testorg", "name": "inst1"}, "state": "Ready"}}
{"data": {"key": {"organization": "testorg", "name": "inst2"}, "state": "Creating"}}
`
		w.WriteHeader(200)
		_, _ = w.Write([]byte(response))
	}))
	defer server.Close()

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

	appInstances, err := client.ShowAppInstances(ctx, AppInstanceKey{Organization: "testorg"}, AppKey{}, "us-west")

	require.NoError(t, err)
	assert.Len(t, appInstances, 2)
	assert.Equal(t, "inst1", appInstances[0].Key.Name)
	assert.Equal(t, "Ready", appInstances[0].State)
	assert.Equal(t, "inst2", appInstances[1].Key.Name)
	assert.Equal(t, "Creating", appInstances[1].State)
}

func TestUpdateAppInstance(t *testing.T) {
	tests := []struct {
		name           string
		input          *UpdateAppInstanceInput
		mockStatusCode int
		mockResponse   string
		expectError    bool
	}{
		{
			name: "successful update",
			input: &UpdateAppInstanceInput{
				Region: "us-west",
				AppInst: AppInstance{
					Key: AppInstanceKey{
						Organization: "testorg",
						Name:         "testinst",
						CloudletKey: CloudletKey{
							Organization: "cloudletorg",
							Name:         "testcloudlet",
						},
					},
					AppKey: AppKey{
						Organization: "testorg",
						Name:         "testapp",
						Version:      "1.0.0",
					},
					Flavor:     Flavor{Name: "m4.medium"},
					PowerState: "PowerOn",
				},
			},
			mockStatusCode: 200,
			mockResponse:   `{"message": "success"}`,
			expectError:    false,
		},
		{
			name: "validation error",
			input: &UpdateAppInstanceInput{
				Region: "us-west",
				AppInst: AppInstance{
					Key: AppInstanceKey{
						Organization: "",
						Name:         "testinst",
						CloudletKey: CloudletKey{
							Organization: "cloudletorg",
							Name:         "testcloudlet",
						},
					},
				},
			},
			mockStatusCode: 400,
			mockResponse:   `{"message": "organization is required"}`,
			expectError:    true,
		},
		{
			name: "instance not found",
			input: &UpdateAppInstanceInput{
				Region: "us-west",
				AppInst: AppInstance{
					Key: AppInstanceKey{
						Organization: "testorg",
						Name:         "nonexistent",
						CloudletKey: CloudletKey{
							Organization: "cloudletorg",
							Name:         "testcloudlet",
						},
					},
				},
			},
			mockStatusCode: 404,
			mockResponse:   `{"message": "app instance 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/UpdateAppInst", r.URL.Path)
				assert.Equal(t, "application/json", r.Header.Get("Content-Type"))

				// Verify request body
				var input UpdateAppInstanceInput
				err := json.NewDecoder(r.Body).Decode(&input)
				require.NoError(t, err)
				assert.Equal(t, tt.input.Region, input.Region)
				assert.Equal(t, tt.input.AppInst.Key.Organization, input.AppInst.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.UpdateAppInstance(ctx, tt.input)

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

func TestRefreshAppInstance(t *testing.T) {
	tests := []struct {
		name           string
		appInstKey     AppInstanceKey
		region         string
		mockStatusCode int
		expectError    bool
	}{
		{
			name: "successful refresh",
			appInstKey: AppInstanceKey{
				Organization: "testorg",
				Name:         "testinst",
				CloudletKey: CloudletKey{
					Organization: "cloudletorg",
					Name:         "testcloudlet",
				},
			},
			region:         "us-west",
			mockStatusCode: 200,
			expectError:    false,
		},
		{
			name: "server error",
			appInstKey: AppInstanceKey{
				Organization: "testorg",
				Name:         "testinst",
				CloudletKey: CloudletKey{
					Organization: "cloudletorg",
					Name:         "testcloudlet",
				},
			},
			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/RefreshAppInst", r.URL.Path)

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

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

			err := client.RefreshAppInstance(ctx, tt.appInstKey, tt.region)

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

func TestDeleteAppInstance(t *testing.T) {
	tests := []struct {
		name           string
		appInstKey     AppInstanceKey
		region         string
		mockStatusCode int
		expectError    bool
	}{
		{
			name: "successful deletion",
			appInstKey: AppInstanceKey{
				Organization: "testorg",
				Name:         "testinst",
				CloudletKey: CloudletKey{
					Organization: "cloudletorg",
					Name:         "testcloudlet",
				},
			},
			region:         "us-west",
			mockStatusCode: 200,
			expectError:    false,
		},
		{
			name: "already deleted (404 ok)",
			appInstKey: AppInstanceKey{
				Organization: "testorg",
				Name:         "testinst",
				CloudletKey: CloudletKey{
					Organization: "cloudletorg",
					Name:         "testcloudlet",
				},
			},
			region:         "us-west",
			mockStatusCode: 404,
			expectError:    false,
		},
		{
			name: "server error",
			appInstKey: AppInstanceKey{
				Organization: "testorg",
				Name:         "testinst",
				CloudletKey: CloudletKey{
					Organization: "cloudletorg",
					Name:         "testcloudlet",
				},
			},
			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/DeleteAppInst", r.URL.Path)

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

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

			err := client.DeleteAppInstance(ctx, tt.appInstKey, tt.region)

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