package integration

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"testing"
	"time"

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

// We define minimal structs to test the responses
type UpsertResult struct {
	Success bool   `json:"success"`
	ID      string `json:"id"`
	Created bool   `json:"created"`
	Errors  []struct {
		Message string `json:"message"`
	} `json:"errors"`
}

type AuthResponse struct {
	AccessToken string `json:"access_token"`
	InstanceURL string `json:"instance_url"`
}

func getBaseURL() string {
	if url := os.Getenv("NEXUSCRM_URL"); url != "" {
		return url
	}
	if url := os.Getenv("API_BASE_URL"); url != "" {
		return url
	}
	return "http://127.0.0.1:3001"
}

// TestUpsertCollection_Strict verifies the Upsert endpoint behavior using raw HTTP
func TestUpsertCollection_Strict(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test")
	}

	baseURL := getBaseURL()
	client := &http.Client{Timeout: 10 * time.Second}

	// 1. Authenticate (Client Credentials Flow)
	// We use the same credentials as other integration tests
	authPayload := map[string]string{
		"grant_type":    "client_credentials",
		"client_id":     "test-gosf-client",
		"client_secret": "test-gosf-secret-123",
	}
	authBody, _ := json.Marshal(authPayload)

	resp, err := client.Post(baseURL+"/services/oauth2/token", "application/json", bytes.NewReader(authBody))
	require.NoError(t, err, "Failed to connect to auth endpoint")
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		t.Skipf("Could not authenticate: status %d. Ensure server is running with test credentials.", resp.StatusCode)
	}

	var authResp AuthResponse
	if err := json.NewDecoder(resp.Body).Decode(&authResp); err != nil {
		t.Fatalf("Failed to decode auth response: %v", err)
	}
	token := authResp.AccessToken
	require.NotEmpty(t, token, "Access token should not be empty")

	// 2. Test Case: Upsert (Insert) using `Id` method
	// We generate a fake ID that looks like a Salesforce ID but is unique to trigger Insert.
	// Note: NexusCRM persistence allows client-supplied IDs on insert.
	// Format: 001 + 15 random chars.
	uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
	if len(uniqueSuffix) > 12 {
		uniqueSuffix = uniqueSuffix[:12]
	}
	newID := fmt.Sprintf("001TestUpsert%s", uniqueSuffix)

	upsertURL := fmt.Sprintf("%s/services/data/v63.0/composite/sobjects/Account/Id", baseURL)

	// Body: allOrNone=true, records=[{Id: newID, Name: "Upserted Account"}]
	reqBody := map[string]interface{}{
		"allOrNone": true,
		"records": []map[string]interface{}{
			{"Id": newID, "Name": "Upserted Account"},
		},
	}
	jsonBody, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("PATCH", upsertURL, bytes.NewReader(jsonBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	resp, err = client.Do(req)
	require.NoError(t, err)
	defer resp.Body.Close()

	bodyBytes, _ := io.ReadAll(resp.Body)
	require.Equal(t, 200, resp.StatusCode, "Upsert (Insert) should return 200. Body: %s", string(bodyBytes))

	var results []UpsertResult
	if err := json.Unmarshal(bodyBytes, &results); err != nil {
		t.Fatalf("Failed to parse result: %v. Body: %s", err, string(bodyBytes))
	}

	require.Equal(t, 1, len(results))
	assert.True(t, results[0].Success)
	assert.True(t, results[0].Created, "Should be created (new ID)")
	assert.Equal(t, newID, results[0].ID)

	// 3. Test Case: Upsert (Update) using the SAME ID
	reqBody["records"] = []map[string]interface{}{
		{"Id": newID, "Name": "Updated Via Upsert"},
	}
	jsonBody, _ = json.Marshal(reqBody)

	req, _ = http.NewRequest("PATCH", upsertURL, bytes.NewReader(jsonBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	resp, err = client.Do(req)
	require.NoError(t, err)
	defer resp.Body.Close()
	bodyBytes, _ = io.ReadAll(resp.Body)

	require.Equal(t, 200, resp.StatusCode, "Upsert (Update) should return 200. Body: %s", string(bodyBytes))

	json.Unmarshal(bodyBytes, &results)
	require.Equal(t, 1, len(results))
	assert.True(t, results[0].Success)
	assert.False(t, results[0].Created, "Should NOT be created (update)")
	assert.Equal(t, newID, results[0].ID)

	// Verify Update via GET
	getReq, _ := http.NewRequest("GET", fmt.Sprintf("%s/services/data/v63.0/sobjects/Account/%s", baseURL, newID), nil)
	getReq.Header.Set("Authorization", "Bearer "+token)
	resp, err = client.Do(getReq)
	require.NoError(t, err)
	defer resp.Body.Close()

	var acc map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&acc)
	assert.Equal(t, "Updated Via Upsert", acc["Name"])

	// 4. Verification Check: Security (Invalid Object)
	// Upsert to /sobjects/InvalidObject/Id
	badURL := fmt.Sprintf("%s/services/data/v63.0/composite/sobjects/InvalidObject123/Id", baseURL)
	req, _ = http.NewRequest("PATCH", badURL, bytes.NewReader(jsonBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, err = client.Do(req)
	require.NoError(t, err)
	defer resp.Body.Close()

	// Should be 404 (Object not found)
	// apierr.NotFound corresponds to 404 Not Found status code
	assert.Equal(t, 404, resp.StatusCode, "Should fail on invalid object due to schema pre-check")

	// 5. Cleanup
	// Delete the created account
	delReq, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/services/data/v63.0/sobjects/Account/%s", baseURL, newID), nil)
	delReq.Header.Set("Authorization", "Bearer "+token)
	client.Do(delReq)
}
