//go:build integration

package dbaasui

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestIntegration_DedicatedCIDRPreparation(t *testing.T) {
	t.Parallel()

	const (
		orgID     = "1440002"
		projectID = "1440002003"
		provider  = "AWS"
		region    = "us-west-2"
		cidr      = "10.251.0.0/16"
		listPath  = "/api/v1/orgs/1440002/projects/1440002003/cidrs?provider=AWS"
		postPath  = "/api/v1/orgs/1440002/projects/1440002003/cidrs"
	)
	rawCreateErrorMessage := " \n cidr \t\n rejected \n" + strings.Repeat("x", 300) + "\t \n"
	wantCreateErrorMessage := "cidr rejected " + strings.Repeat("x", 256-len("cidr rejected "))
	cases := []struct {
		name                  string
		initial               []dedicatedCIDRRecord
		createStatus          int
		createResponseID      string
		readBackID            string
		createErrorCode       string
		createErrorMsg        string
		createErrorMsgRaw     string
		successBaseResp       bool
		successBaseRespCode   string
		successBaseRespMsgRaw string
		listErrorCode         string
		listErrorMessage      string
		listErrorMessageRaw   string
		wantAction            string
		wantReceipt           DedicatedCIDRReceipt
		wantCIDRCreates       int
		wantRequests          []string
		wantError             bool
		wantErrorText         string
	}{
		{
			name: "existing reuses without duplicate",
			initial: []dedicatedCIDRRecord{{
				ID:        "cidr-existing",
				ProjectID: projectID,
				CIDR:      cidr,
				Provider:  provider,
				State:     "STATE_ACTIVE",
				Region:    dedicatedCIDRRegion{Name: region, Provider: provider},
			}},
			wantAction: "reused",
			wantReceipt: DedicatedCIDRReceipt{
				Action:             "reused",
				Baseline:           "existing",
				CIDR:               cidr,
				EffectiveOrgID:     orgID,
				EffectiveProjectID: projectID,
				ID:                 "cidr-existing",
				Provider:           provider,
				Region:             region,
				RequestedOrgID:     orgID,
				RequestedProjectID: projectID,
			},
			wantCIDRCreates: 0,
			wantRequests:    []string{"GET " + listPath},
		},
		{
			name: "existing reuses with empty application code",
			initial: []dedicatedCIDRRecord{{
				ID:        "cidr-existing",
				ProjectID: projectID,
				CIDR:      cidr,
				Provider:  provider,
				State:     "STATE_ACTIVE",
				Region:    dedicatedCIDRRegion{Name: region, Provider: provider},
			}},
			successBaseResp:     true,
			successBaseRespCode: "",
			wantAction:          "reused",
			wantReceipt: DedicatedCIDRReceipt{
				Action:             "reused",
				Baseline:           "existing",
				CIDR:               cidr,
				EffectiveOrgID:     orgID,
				EffectiveProjectID: projectID,
				ID:                 "cidr-existing",
				Provider:           provider,
				Region:             region,
				RequestedOrgID:     orgID,
				RequestedProjectID: projectID,
			},
			wantCIDRCreates: 0,
			wantRequests:    []string{"GET " + listPath},
		},
		{
			name:             "list application error stops before create",
			listErrorCode:    "701",
			listErrorMessage: "list unavailable",
			wantRequests:     []string{"GET " + listPath},
			wantError:        true,
			wantErrorText:    `list Dedicated CIDRs: dedicated CIDR API error code=701 message="list unavailable"`,
		},
		{
			name:                "list code survives malformed message",
			listErrorCode:       "703",
			listErrorMessageRaw: `{"secret":"list-secret"}`,
			wantRequests:        []string{"GET " + listPath},
			wantError:           true,
			wantErrorText:       "list Dedicated CIDRs: dedicated CIDR API error code=703",
		},
		{
			name:            "create application error wins before id readback",
			createStatus:    http.StatusOK,
			createErrorCode: "702",
			createErrorMsg:  rawCreateErrorMessage,
			wantCIDRCreates: 1,
			wantRequests:    []string{"GET " + listPath, "POST " + postPath},
			wantError:       true,
			wantErrorText:   `create Dedicated CIDR: dedicated CIDR API error code=702 message="` + wantCreateErrorMessage + `"`,
		},
		{
			name:              "create code survives malformed message",
			createStatus:      http.StatusOK,
			createErrorCode:   "704",
			createErrorMsgRaw: `["create-secret"]`,
			wantCIDRCreates:   1,
			wantRequests:      []string{"GET " + listPath, "POST " + postPath},
			wantError:         true,
			wantErrorText:     "create Dedicated CIDR: dedicated CIDR API error code=704",
		},
		{
			name:          "malformed list application code stops before create",
			listErrorCode: "not-a-number",
			wantRequests:  []string{"GET " + listPath},
			wantError:     true,
			wantErrorText: "list Dedicated CIDRs: dedicated CIDR API error: malformed err_code",
		},
		{
			name:                  "absent creates once and reads back",
			createStatus:          http.StatusOK,
			createResponseID:      "cidr-created",
			readBackID:            "cidr-created",
			successBaseResp:       true,
			successBaseRespCode:   "0",
			successBaseRespMsgRaw: `{"ignored":"zero-code-message"}`,
			wantAction:            "created",
			wantReceipt: DedicatedCIDRReceipt{
				Action:             "created",
				Baseline:           "absent",
				CIDR:               cidr,
				EffectiveOrgID:     orgID,
				EffectiveProjectID: projectID,
				ID:                 "cidr-created",
				Provider:           provider,
				Region:             region,
				RequestedOrgID:     orgID,
				RequestedProjectID: projectID,
			},
			wantCIDRCreates: 1,
			wantRequests:    []string{"GET " + listPath, "POST " + postPath, "GET " + listPath},
		},
		{
			name:             "create and read back ID mismatch fails",
			createStatus:     http.StatusOK,
			createResponseID: "cidr-created",
			readBackID:       "cidr-different",
			wantCIDRCreates:  1,
			wantRequests:     []string{"GET " + listPath, "POST " + postPath, "GET " + listPath},
			wantError:        true,
		},
		{
			name:            "create failure returns before read back",
			createStatus:    http.StatusBadGateway,
			wantCIDRCreates: 1,
			wantRequests:    []string{"GET " + listPath, "POST " + postPath},
			wantError:       true,
		},
	}
	baseResponse := func(code, message, raw string) map[string]any {
		response := map[string]any{"err_code": code, "err_msg": message}
		if raw != "" {
			response["err_msg"] = json.RawMessage(raw)
		}
		return response
	}

	for _, tt := range cases {
		t.Run(tt.name, func(t *testing.T) {
			items := append([]dedicatedCIDRRecord(nil), tt.initial...)
			var requests []string
			cidrCreates := 0
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				requests = append(requests, r.Method+" "+r.URL.RequestURI())
				if got := r.Header.Get("Accept"); got != "application/json" {
					t.Errorf("unexpected Accept header: %q", got)
				}
				switch {
				case r.Method == http.MethodGet && r.URL.RequestURI() == listPath:
					w.Header().Set("Content-Type", "application/json")
					if tt.listErrorCode != "" {
						_ = json.NewEncoder(w).Encode(map[string]any{
							"items":     []any{},
							"total":     0,
							"base_resp": baseResponse(tt.listErrorCode, tt.listErrorMessage, tt.listErrorMessageRaw),
						})
						return
					}
					legacyItems := make([]map[string]any, 0, len(items))
					for _, item := range items {
						legacyItems = append(legacyItems, map[string]any{
							"id":         item.ID,
							"project_id": item.ProjectID,
							"cidr":       item.CIDR,
							"provider":   item.Provider,
							"state":      item.State,
							"region": map[string]string{
								"name":     item.Region.Name,
								"provider": item.Region.Provider,
							},
						})
					}
					listResponse := map[string]any{"items": legacyItems}
					if tt.successBaseResp {
						listResponse["total"] = len(legacyItems)
						listResponse["base_resp"] = baseResponse(tt.successBaseRespCode, "", tt.successBaseRespMsgRaw)
					}
					_ = json.NewEncoder(w).Encode(listResponse)
				case r.Method == http.MethodPost && r.URL.RequestURI() == postPath:
					cidrCreates++
					if tt.listErrorCode != "" {
						http.Error(w, "unexpected CIDR create after list application error", http.StatusInternalServerError)
						return
					}
					if tt.createStatus != http.StatusOK {
						http.Error(w, "cidr create failed", tt.createStatus)
						return
					}
					if tt.createErrorCode != "" {
						_ = json.NewEncoder(w).Encode(map[string]any{
							"base_resp": baseResponse(tt.createErrorCode, tt.createErrorMsg, tt.createErrorMsgRaw),
						})
						return
					}
					var body struct {
						CIDR   string              `json:"cidr"`
						Region dedicatedCIDRRegion `json:"region"`
					}
					if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
						t.Errorf("decode CIDR create body: %v", err)
						return
					}
					if body.CIDR != cidr || body.Region.Name != region || body.Region.Provider != provider {
						t.Errorf("unexpected CIDR create body: %+v", body)
					}
					responseID := tt.createResponseID
					if responseID == "" {
						responseID = "cidr-created"
					}
					readBackID := tt.readBackID
					if readBackID == "" {
						readBackID = responseID
					}
					items = append(items, dedicatedCIDRRecord{
						ID:        readBackID,
						ProjectID: projectID,
						CIDR:      body.CIDR,
						Provider:  body.Region.Provider,
						State:     "STATE_ACTIVE",
						Region:    body.Region,
					})
					w.Header().Set("Content-Type", "application/json")
					createResponse := map[string]any{"id": responseID}
					if tt.successBaseResp {
						createResponse["base_resp"] = baseResponse(tt.successBaseRespCode, "", tt.successBaseRespMsgRaw)
					}
					_ = json.NewEncoder(w).Encode(createResponse)
				default:
					http.NotFound(w, r)
				}
			}))
			defer server.Close()

			receipt, err := PrepareDedicatedCIDR(
				context.Background(),
				server.Client(),
				server.URL,
				orgID,
				projectID,
				provider,
				region,
				cidr,
				"integration-test-token",
			)
			if tt.wantError {
				if err == nil {
					t.Fatal("expected CIDR preparation error")
				}
				if tt.wantErrorText != "" && err.Error() != tt.wantErrorText {
					t.Errorf("unexpected CIDR preparation error: got=%q want=%q", err, tt.wantErrorText)
				}
			} else {
				if err != nil {
					t.Fatalf("prepare CIDR: %v", err)
				}
				if receipt.Action != tt.wantAction || receipt != tt.wantReceipt {
					t.Fatalf("unexpected CIDR receipt: got=%+v want=%+v", receipt, tt.wantReceipt)
				}
			}
			if cidrCreates != tt.wantCIDRCreates {
				t.Fatalf("unexpected CIDR create count: got=%d want=%d", cidrCreates, tt.wantCIDRCreates)
			}
			if len(requests) != len(tt.wantRequests) {
				t.Fatalf("unexpected request count: got=%v want=%v", requests, tt.wantRequests)
			}
			for i := range tt.wantRequests {
				if requests[i] != tt.wantRequests[i] {
					t.Fatalf("unexpected request order: got=%v want=%v", requests, tt.wantRequests)
				}
			}
		})
	}
}
