package dialectsfcrm

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"strings"
	"testing"

	"github.com/nexuscrm/dialect-sf-crm/wire"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/dialect"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestListReportsNeutralHandlerProjectsNeutralRows(t *testing.T) {
	query := &dashboardRouteQueryStub{
		result: &contract.QueryResult{Records: []contract.Record{{
			models.ObjectTypeMetadataKey: "Report",
			"attributes":                 map[string]interface{}{"type": "WrongReport", "url": "/old"},
			"_metadata":                  map[string]interface{}{"internal": true},
			"Id":                         "00Oxx0000000001AAA",
			"Name":                       "Pipeline by Region",
			"DeveloperName":              "Pipeline_by_Region",
			"FolderId":                   "00lxx0000000001AAA",
			"Description":                "Visible report",
			"ReportType":                 "OpportunityReport",
			"SourceObject":               "Opportunity",
			"InternalOnly":               "must not leak",
		}}},
	}
	resp, err := ListReportsNeutralHandler(context.Background(), &dialect.Request{
		User:  &models.UserSession{ID: "005-viewer"},
		Query: map[string][]string{"limit": {"5"}},
	}, dialect.Deps{Contracts: contract.Deps{Query: query}})
	if err != nil {
		t.Fatalf("ListReportsNeutralHandler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	assertReportRouteJSON(t, resp.Body, `{
		"reports": [
			{
				"id": "00Oxx0000000001AAA",
				"name": "Pipeline by Region",
				"developerName": "Pipeline_by_Region",
				"folderId": "00lxx0000000001AAA",
				"description": "Visible report",
				"reportType": "OpportunityReport",
				"sourceObject": "Opportunity"
			}
		],
		"totalSize": 1
	}`)
	if query.req.ObjectAPIName != constants.TableReport {
		t.Fatalf("query object = %q, want %q", query.req.ObjectAPIName, constants.TableReport)
	}
}

func TestReportMutationNeutralHandlersProjectNeutralRecords(t *testing.T) {
	t.Run("create returns id and stores neutral report definition", func(t *testing.T) {
		records := &reportRouteRecordsStub{
			dashboardRouteRecordsStub: dashboardRouteRecordsStub{
				insertResult: contract.Record{
					models.ObjectTypeMetadataKey: "Report",
					"attributes":                 map[string]any{"type": "WrongReport", "url": "/old"},
					"Id":                         "00Oxx0000000002AAA",
					"InternalOnly":               "must not leak",
				},
			},
		}
		resp, err := CreateReportNeutralHandler(context.Background(), &dialect.Request{
			User: &models.UserSession{ID: "005-viewer"},
			Body: []byte(`{
				"reportMetadata": {
					"name": "Pipeline by Region",
					"reportType": { "type": "OpportunityReport" },
					"detailColumns": ["Name"],
					"reportFilters": [{"column":"StageName","operator":"equals","value":"Prospecting"}]
				}
			}`),
		}, dialect.Deps{Contracts: contract.Deps{
			Records: records,
		}})
		if err != nil {
			t.Fatalf("CreateReportNeutralHandler error: %v", err)
		}
		if resp.Status != http.StatusCreated {
			t.Fatalf("status = %d, want 201; body=%#v", resp.Status, resp.Body)
		}
		assertReportRouteJSON(t, resp.Body, `{"id": "00Oxx0000000002AAA"}`)
		if records.insertedObject != constants.TableReport {
			t.Fatalf("insert object = %q, want %q", records.insertedObject, constants.TableReport)
		}
		if records.insertedData["DeveloperName"] != "Pipeline_by_Region" {
			t.Fatalf("DeveloperName = %v, want Pipeline_by_Region", records.insertedData["DeveloperName"])
		}
		if records.insertedData["SourceObject"] != "Opportunity" {
			t.Fatalf("SourceObject = %v, want Opportunity", records.insertedData["SourceObject"])
		}
		if records.insertedData["Filters"] != `[{"column":"StageName","operator":"equals","value":"Prospecting"}]` {
			t.Fatalf("Filters stored as %v", records.insertedData["Filters"])
		}
	})

	t.Run("update returns report response", func(t *testing.T) {
		records := &reportRouteRecordsStub{
			dashboardRouteRecordsStub: dashboardRouteRecordsStub{
				retrieveResult: contract.Record{
					models.ObjectTypeMetadataKey: "Report",
					"attributes":                 map[string]any{"type": "WrongReport", "url": "/old"},
					"Id":                         "00Oxx0000000003AAA",
					"Name":                       "Updated Pipeline",
					"DeveloperName":              "Updated_Pipeline",
					"FolderId":                   "00lxx0000000010AAA",
					"Description":                "Updated report",
					"ReportType":                 "OpportunityReport",
					"SourceObject":               "Opportunity",
					"InternalOnly":               "must not leak",
				},
			},
		}
		resp, err := UpdateReportNeutralHandler(context.Background(), &dialect.Request{
			User:   &models.UserSession{ID: "005-viewer"},
			Params: map[string]string{"reportId": "00Oxx0000000003AAA"},
			Body: []byte(`{
				"reportMetadata": {
					"name": "Updated Pipeline"
				}
			}`),
		}, dialect.Deps{Contracts: contract.Deps{Records: records}})
		if err != nil {
			t.Fatalf("UpdateReportNeutralHandler error: %v", err)
		}
		if resp.Status != http.StatusOK {
			t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
		}
		assertReportRouteJSON(t, resp.Body, `{
			"id": "00Oxx0000000003AAA",
			"name": "Updated Pipeline",
			"developerName": "Updated_Pipeline",
			"folderId": "00lxx0000000010AAA",
			"description": "Updated report",
			"reportType": "OpportunityReport",
			"sourceObject": "Opportunity"
		}`)
		if records.updatedObject != constants.TableReport {
			t.Fatalf("update object = %q, want %q", records.updatedObject, constants.TableReport)
		}
		if records.updatedData["Name"] != "Updated Pipeline" {
			t.Fatalf("updated Name = %v, want Updated Pipeline", records.updatedData["Name"])
		}
	})
}

func TestReportMutationNeutralHandlersNormalizeReportSourceTokens(t *testing.T) {
	t.Run("create source object only persists single-object report type token", func(t *testing.T) {
		records := &reportRouteRecordsStub{
			dashboardRouteRecordsStub: dashboardRouteRecordsStub{
				insertResult: contract.Record{"Id": "00Oxx0000000009AAA"},
			},
		}
		resp, err := CreateReportNeutralHandler(context.Background(), &dialect.Request{
			User: &models.UserSession{ID: "005-viewer"},
			Body: []byte(`{
				"reportMetadata": {
					"name": "Pipeline",
					"sourceObject": "Opportunity"
				}
			}`),
		}, dialect.Deps{Contracts: contract.Deps{
			Records: records,
		}})
		if err != nil {
			t.Fatalf("CreateReportNeutralHandler error: %v", err)
		}
		if resp.Status != http.StatusCreated {
			t.Fatalf("status = %d, want 201; body=%#v", resp.Status, resp.Body)
		}
		if records.insertedData["SourceObject"] != "Opportunity" {
			t.Fatalf("SourceObject = %v, want Opportunity", records.insertedData["SourceObject"])
		}
		if records.insertedData["SourceRef"] != "OpportunityList" {
			t.Fatalf("SourceRef = %v, want OpportunityList", records.insertedData["SourceRef"])
		}
		if records.insertedData["ReportType"] != "OpportunityList" {
			t.Fatalf("ReportType = %v, want OpportunityList", records.insertedData["ReportType"])
		}
	})

	t.Run("update source object only persists single-object report type token", func(t *testing.T) {
		records := &reportRouteRecordsStub{
			dashboardRouteRecordsStub: dashboardRouteRecordsStub{
				retrieveResult: contract.Record{"Id": "00Oxx0000000009AAB"},
			},
		}
		resp, err := UpdateReportNeutralHandler(context.Background(), &dialect.Request{
			User:   &models.UserSession{ID: "005-viewer"},
			Params: map[string]string{"reportId": "00Oxx0000000009AAB"},
			Body: []byte(`{
				"reportMetadata": {
					"sourceObject": "Opportunity"
				}
			}`),
		}, dialect.Deps{Contracts: contract.Deps{
			Records: records,
		}})
		if err != nil {
			t.Fatalf("UpdateReportNeutralHandler error: %v", err)
		}
		if resp.Status != http.StatusOK {
			t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
		}
		if records.updatedData["SourceObject"] != "Opportunity" {
			t.Fatalf("SourceObject = %v, want Opportunity", records.updatedData["SourceObject"])
		}
		if records.updatedData["SourceRef"] != "OpportunityList" {
			t.Fatalf("SourceRef = %v, want OpportunityList", records.updatedData["SourceRef"])
		}
		if records.updatedData["ReportType"] != "OpportunityList" {
			t.Fatalf("ReportType = %v, want OpportunityList", records.updatedData["ReportType"])
		}
	})

	t.Run("create resolves CRT report type and persists source ref", func(t *testing.T) {
		records := &reportRouteRecordsStub{
			dashboardRouteRecordsStub: dashboardRouteRecordsStub{
				insertResult: contract.Record{"Id": "00Oxx0000000010AAA"},
			},
		}
		reports := &reportRouteReportsStub{sourceBaseObjects: map[string]string{"IssueWithComments": "Issue"}}
		resp, err := CreateReportNeutralHandler(context.Background(), &dialect.Request{
			User: &models.UserSession{ID: "005-viewer"},
			Body: []byte(`{
				"reportMetadata": {
					"name": "Issues with Comments",
					"reportType": { "type": "IssueWithComments" }
				}
			}`),
		}, dialect.Deps{Contracts: contract.Deps{
			Records: records,
			Reports: reports,
		}})
		if err != nil {
			t.Fatalf("CreateReportNeutralHandler error: %v", err)
		}
		if resp.Status != http.StatusCreated {
			t.Fatalf("status = %d, want 201; body=%#v", resp.Status, resp.Body)
		}
		if records.insertedData["SourceObject"] != "Issue" {
			t.Fatalf("SourceObject = %v, want Issue", records.insertedData["SourceObject"])
		}
		if records.insertedData["SourceRef"] != "IssueWithComments" {
			t.Fatalf("SourceRef = %v, want IssueWithComments", records.insertedData["SourceRef"])
		}
		if records.insertedData["ReportType"] != "IssueWithComments" {
			t.Fatalf("ReportType = %v, want IssueWithComments", records.insertedData["ReportType"])
		}
	})

	t.Run("update report type only resolves source object", func(t *testing.T) {
		records := &reportRouteRecordsStub{
			dashboardRouteRecordsStub: dashboardRouteRecordsStub{
				retrieveResult: contract.Record{"Id": "00Oxx0000000011AAA"},
			},
		}
		reports := &reportRouteReportsStub{sourceBaseObjects: map[string]string{"IssueWithComments": "Issue"}}
		resp, err := UpdateReportNeutralHandler(context.Background(), &dialect.Request{
			User:   &models.UserSession{ID: "005-viewer"},
			Params: map[string]string{"reportId": "00Oxx0000000011AAA"},
			Body: []byte(`{
				"reportMetadata": {
					"reportType": { "type": "IssueWithComments" }
				}
			}`),
		}, dialect.Deps{Contracts: contract.Deps{
			Records: records,
			Reports: reports,
		}})
		if err != nil {
			t.Fatalf("UpdateReportNeutralHandler error: %v", err)
		}
		if resp.Status != http.StatusOK {
			t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
		}
		if records.updatedData["SourceObject"] != "Issue" {
			t.Fatalf("SourceObject = %v, want Issue", records.updatedData["SourceObject"])
		}
		if records.updatedData["SourceRef"] != "IssueWithComments" {
			t.Fatalf("SourceRef = %v, want IssueWithComments", records.updatedData["SourceRef"])
		}
		if records.updatedData["ReportType"] != "IssueWithComments" {
			t.Fatalf("ReportType = %v, want IssueWithComments", records.updatedData["ReportType"])
		}
	})
}

func TestReportRuntimeNeutralHandlersNormalizeReportSourceTokens(t *testing.T) {
	reports := &reportRouteReportsStub{sourceBaseObjects: map[string]string{"IssueWithComments": "Issue"}}
	resp, err := ExecuteReportDefinitionNeutralHandler(context.Background(), &dialect.Request{
		User: &models.UserSession{ID: "005-viewer"},
		Body: []byte(`{
			"reportMetadata": {
				"name": "Issues with Comments",
				"reportType": { "type": "IssueWithComments" }
			}
		}`),
	}, dialect.Deps{Contracts: contract.Deps{Reports: reports}})
	if err != nil {
		t.Fatalf("ExecuteReportDefinitionNeutralHandler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	if reports.runDefinition == nil {
		t.Fatal("RunReportDefinition was not called")
	}
	if reports.runDefinition.SourceObject != "Issue" {
		t.Fatalf("SourceObject = %q, want Issue", reports.runDefinition.SourceObject)
	}
	if reports.runDefinition.SourceRef != "IssueWithComments" {
		t.Fatalf("SourceRef = %q, want IssueWithComments", reports.runDefinition.SourceRef)
	}
}

func TestReportSourceNormalizationFailsClosedForUnresolvedReportTypeOnlyPatch(t *testing.T) {
	records := &reportRouteRecordsStub{}
	reports := &reportRouteReportsStub{}
	resp, err := UpdateReportNeutralHandler(context.Background(), &dialect.Request{
		User:   &models.UserSession{ID: "005-viewer"},
		Params: map[string]string{"reportId": "00Oxx0000000012AAA"},
		Body: []byte(`{
			"reportMetadata": {
				"reportType": { "type": "IssueWithComments" }
			}
		}`),
	}, dialect.Deps{Contracts: contract.Deps{
		Records: records,
		Reports: reports,
	}})
	if err != nil {
		t.Fatalf("UpdateReportNeutralHandler error: %v", err)
	}
	if resp.Status != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400; body=%#v", resp.Status, resp.Body)
	}
	if records.updatedObject != "" {
		t.Fatalf("updated object = %q, want no update", records.updatedObject)
	}
}

func TestReportMutationNeutralHandlersPersistDisplayMode(t *testing.T) {
	t.Run("create stores explicit matrix report format", func(t *testing.T) {
		records := &reportRouteRecordsStub{
			dashboardRouteRecordsStub: dashboardRouteRecordsStub{
				insertResult: contract.Record{"Id": "00Oxx0000000013AAA"},
			},
		}
		resp, err := CreateReportNeutralHandler(context.Background(), &dialect.Request{
			User: &models.UserSession{ID: "005-viewer"},
			Body: []byte(`{
				"reportMetadata": {
					"name": "Matrix Pipeline",
					"sourceObject": "Opportunity",
					"reportFormat": "MATRIX"
				}
			}`),
		}, dialect.Deps{Contracts: contract.Deps{Records: records}})
		if err != nil {
			t.Fatalf("CreateReportNeutralHandler error: %v", err)
		}
		if resp.Status != http.StatusCreated {
			t.Fatalf("status = %d, want 201; body=%#v", resp.Status, resp.Body)
		}
		if records.insertedData["DisplayMode"] != "MATRIX" {
			t.Fatalf("DisplayMode = %v, want MATRIX", records.insertedData["DisplayMode"])
		}
	})

	t.Run("update stores explicit matrix report format", func(t *testing.T) {
		records := &reportRouteRecordsStub{
			dashboardRouteRecordsStub: dashboardRouteRecordsStub{
				retrieveResult: contract.Record{"Id": "00Oxx0000000014AAA"},
			},
		}
		resp, err := UpdateReportNeutralHandler(context.Background(), &dialect.Request{
			User:   &models.UserSession{ID: "005-viewer"},
			Params: map[string]string{"reportId": "00Oxx0000000014AAA"},
			Body: []byte(`{
				"reportMetadata": {
					"reportFormat": "MATRIX"
				}
			}`),
		}, dialect.Deps{Contracts: contract.Deps{Records: records}})
		if err != nil {
			t.Fatalf("UpdateReportNeutralHandler error: %v", err)
		}
		if resp.Status != http.StatusOK {
			t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
		}
		if records.updatedData["DisplayMode"] != "MATRIX" {
			t.Fatalf("DisplayMode = %v, want MATRIX", records.updatedData["DisplayMode"])
		}
	})
}

func TestReportRuntimeNeutralHandlersUseSalesforceWireAdapters(t *testing.T) {
	reports := &reportRouteReportsStub{
		metadata: &models.ReportDefinition{
			ID:           "00Oxx0000000004AAA",
			Name:         "Pipeline",
			SourceObject: "Opportunity",
			SourceRef:    "OpportunityReport",
			DisplayMode:  "TABULAR",
			Columns:      []string{"Name"},
		},
		result: &models.ReportResult{
			ReportMetadata: models.ReportDefinition{
				ID:           "00Oxx0000000004AAA",
				Name:         "Pipeline",
				SourceObject: "Opportunity",
				SourceRef:    "OpportunityReport",
				DisplayMode:  "TABULAR",
				Columns:      []string{"Name"},
			},
			Rows:       []models.SObject{{"Name": "Acme"}},
			TotalCount: 1,
		},
	}
	deps := dialect.Deps{Contracts: contract.Deps{
		Reports: reports,
		Metadata: dashboardRouteMetadata{schemas: map[string]*models.ObjectMetadata{
			"Opportunity": {APIName: "Opportunity", Fields: []models.FieldMetadata{{APIName: "Name", Label: "Opportunity Name"}}},
		}},
	}}

	resp, err := RunReportExecutionNeutralHandler(context.Background(), &dialect.Request{
		User: &models.UserSession{ID: "005-viewer"},
		Body: []byte(`{
			"reportMetadata": {
				"name": "Ad hoc Pipeline",
				"sourceObject": "Opportunity",
				"detailColumns": ["Name"]
			},
			"limit": 5
		}`),
	}, deps)
	if err != nil {
		t.Fatalf("RunReportExecutionNeutralHandler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	assertReportRouteJSON(t, resp.Body, `{
		"allData": true,
		"factMap": {
			"T!T": {
				"aggregates": [{"label": "1", "value": 1}],
				"rows": [{
					"dataCells": [{"label": "Acme", "value": "Acme"}]
				}]
			}
		},
		"groupingsDown": {"groupings": []},
		"hasDetailRows": true,
		"reportMetadata": {
			"aggregates": ["RowCount"],
			"detailColumns": ["Name"],
			"id": "00Oxx0000000004AAA",
			"name": "Pipeline",
			"reportExtendedMetadata": {
				"aggregateColumnInfo": {
					"RowCount": {
						"dataType": "int",
						"label": "Record Count"
					}
				},
				"detailColumnInfo": {
					"Name": {
						"dataType": "string",
						"label": "Name"
					}
				},
				"groupingColumnInfo": {}
			},
			"reportFormat": "TABULAR",
			"reportType": {"label": "Opportunity", "type": "OpportunityReport"},
			"sourceObject": "Opportunity"
		}
	}`)
	if reports.runLimit != 5 {
		t.Fatalf("run limit = %d, want 5", reports.runLimit)
	}
}

func TestReportNeutralHandlersPreserveSalesforceRequiredFieldMessages(t *testing.T) {
	tests := []struct {
		name   string
		invoke func() (*dialect.Response, error)
		want   string
	}{
		{
			name: "create missing name",
			invoke: func() (*dialect.Response, error) {
				return CreateReportNeutralHandler(context.Background(), &dialect.Request{
					Body: []byte(`{"reportMetadata":{"reportType":{"type":"OpportunityReport"}}}`),
				}, dialect.Deps{Contracts: contract.Deps{Records: &reportRouteRecordsStub{}}})
			},
			want: `[{"errorCode":"REQUIRED_FIELD_MISSING","message":"reportMetadata.name is required"}]`,
		},
		{
			name: "create missing report type and source object",
			invoke: func() (*dialect.Response, error) {
				return CreateReportNeutralHandler(context.Background(), &dialect.Request{
					Body: []byte(`{"reportMetadata":{"name":"Pipeline"}}`),
				}, dialect.Deps{Contracts: contract.Deps{Records: &reportRouteRecordsStub{}}})
			},
			want: `[{"errorCode":"REQUIRED_FIELD_MISSING","message":"reportMetadata.reportType or reportMetadata.sourceObject is required"}]`,
		},
		{
			name: "execute missing name",
			invoke: func() (*dialect.Response, error) {
				return ExecuteReportDefinitionNeutralHandler(context.Background(), &dialect.Request{
					Body: []byte(`{"reportMetadata":{"reportType":"OpportunityReport"}}`),
				}, dialect.Deps{Contracts: contract.Deps{Reports: &reportRouteReportsStub{}}})
			},
			want: `[{"errorCode":"REQUIRED_FIELD_MISSING","message":"reportMetadata.name is required"}]`,
		},
		{
			name: "run execution missing id and metadata",
			invoke: func() (*dialect.Response, error) {
				return RunReportExecutionNeutralHandler(context.Background(), &dialect.Request{
					Body: []byte(`{}`),
				}, dialect.Deps{Contracts: contract.Deps{Reports: &reportRouteReportsStub{}}})
			},
			want: `[{"errorCode":"REQUIRED_FIELD_MISSING","message":"reportId or reportMetadata is required"}]`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			resp, err := tt.invoke()
			if err != nil {
				t.Fatalf("handler error: %v", err)
			}
			if resp.Status != http.StatusBadRequest {
				t.Fatalf("status = %d, want 400; body=%#v", resp.Status, resp.Body)
			}
			assertBody(t, tt.name, resp.Body, tt.want, tt.want)
		})
	}
}

func TestDeleteReportNeutralHandlerUsesReportObject(t *testing.T) {
	records := &reportRouteRecordsStub{}
	resp, err := DeleteReportNeutralHandler(context.Background(), &dialect.Request{
		User:   &models.UserSession{ID: "005-viewer"},
		Params: map[string]string{"reportId": "00Oxx0000000005AAA"},
	}, dialect.Deps{Contracts: contract.Deps{Records: records}})
	if err != nil {
		t.Fatalf("DeleteReportNeutralHandler error: %v", err)
	}
	if resp.Status != http.StatusNoContent {
		t.Fatalf("status = %d, want 204; body=%#v", resp.Status, resp.Body)
	}
	if records.deletedObject != constants.TableReport {
		t.Fatalf("delete object = %q, want %q", records.deletedObject, constants.TableReport)
	}
}

func TestReportHandlersUseGenericInternalErrorForMissingContracts(t *testing.T) {
	tests := []struct {
		name   string
		invoke func() (*dialect.Response, error)
	}{
		{
			name: "list query",
			invoke: func() (*dialect.Response, error) {
				return ListReportsNeutralHandler(context.Background(), &dialect.Request{}, dialect.Deps{})
			},
		},
		{
			name: "execute reports",
			invoke: func() (*dialect.Response, error) {
				return ExecuteReportDefinitionNeutralHandler(context.Background(), &dialect.Request{
					Body: []byte(`{"reportMetadata":{"name":"Pipeline","sourceObject":"Opportunity"}}`),
				}, dialect.Deps{})
			},
		},
		{
			name: "create records",
			invoke: func() (*dialect.Response, error) {
				return CreateReportNeutralHandler(context.Background(), &dialect.Request{
					Body: []byte(`{"reportMetadata":{"name":"Pipeline","sourceObject":"Opportunity"}}`),
				}, dialect.Deps{})
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			resp, err := tt.invoke()
			if err != nil {
				t.Fatalf("handler error: %v", err)
			}
			assertSFError(t, resp, http.StatusInternalServerError, wire.SFCodeInternalError)
		})
	}
}

func TestReportHandlersLogInternalErrors(t *testing.T) {
	tests := []struct {
		name   string
		secret string
		invoke func() (*dialect.Response, error)
	}{
		{
			name:   "list query internal error",
			secret: "driver secret: report list sql",
			invoke: func() (*dialect.Response, error) {
				return ListReportsNeutralHandler(context.Background(), &dialect.Request{Version: "v63.0"}, dialect.Deps{Contracts: contract.Deps{
					Query: &dashboardRouteQueryStub{err: reportRouteInternalError{msg: "query failed", cause: errors.New("driver secret: report list sql")}},
				}})
			},
		},
		{
			name:   "execute report unknown error",
			secret: "runner secret: report aggregate panic",
			invoke: func() (*dialect.Response, error) {
				return ExecuteReportDefinitionNeutralHandler(context.Background(), &dialect.Request{
					Version: "v63.0",
					Body:    []byte(`{"reportMetadata":{"name":"Pipeline","sourceObject":"Opportunity"}}`),
				}, dialect.Deps{Contracts: contract.Deps{
					Reports: &reportRouteReportsStub{runErr: errors.New("runner secret: report aggregate panic")},
				}})
			},
		},
		{
			name:   "create report write internal error",
			secret: "driver secret: insert report",
			invoke: func() (*dialect.Response, error) {
				return CreateReportNeutralHandler(context.Background(), &dialect.Request{
					Version: "v63.0",
					Body:    []byte(`{"reportMetadata":{"name":"Pipeline","sourceObject":"Opportunity"}}`),
				}, dialect.Deps{Contracts: contract.Deps{
					Records: &reportRouteRecordsStub{insertErr: reportRouteInternalError{msg: "insert failed", cause: errors.New("driver secret: insert report")}},
				}})
			},
		},
		{
			name:   "update report retrieve internal error",
			secret: "driver secret: retrieve updated report",
			invoke: func() (*dialect.Response, error) {
				return UpdateReportNeutralHandler(context.Background(), &dialect.Request{
					Version: "v63.0",
					Params:  map[string]string{"reportId": "00Oxx0000000006AAA"},
					Body:    []byte(`{"reportMetadata":{"name":"Pipeline"}}`),
				}, dialect.Deps{Contracts: contract.Deps{
					Records: &reportRouteRecordsStub{retrieveErr: reportRouteInternalError{msg: "retrieve failed", cause: errors.New("driver secret: retrieve updated report")}},
				}})
			},
		},
		{
			name:   "delete report unknown write error",
			secret: "driver secret: delete report preflight",
			invoke: func() (*dialect.Response, error) {
				return DeleteReportNeutralHandler(context.Background(), &dialect.Request{
					Version: "v63.0",
					Params:  map[string]string{"reportId": "00Oxx0000000008AAA"},
				}, dialect.Deps{Contracts: contract.Deps{
					Records: &reportRouteRecordsStub{deleteErr: errors.New("driver secret: delete report preflight")},
				}})
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var resp *dialect.Response
			logs := captureReportRouteLogs(t, func() {
				var err error
				resp, err = tt.invoke()
				if err != nil {
					t.Fatalf("handler error: %v", err)
				}
			})
			assertReportGenericInternalError(t, resp, tt.secret)
			if !strings.Contains(logs, tt.secret) {
				t.Fatalf("logs = %q, want internal cause %q", logs, tt.secret)
			}
		})
	}
}

func TestReportServiceValidationErrorsRemainClientErrors(t *testing.T) {
	tests := []struct {
		name     string
		runErr   error
		wantBody []string
	}{
		{
			name:     "field scoped validation error",
			runErr:   NewValidationError("detailColumns", "unsupported aggregate shape"),
			wantBody: []string{"unsupported aggregate shape", "detailColumns"},
		},
		{
			name:     "plain status bad request",
			runErr:   statusError{status: http.StatusBadRequest, message: "bad dot-notation detail column"},
			wantBody: []string{"bad dot-notation detail column"},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var resp *dialect.Response
			logs := captureReportRouteLogs(t, func() {
				var err error
				resp, err = ExecuteReportDefinitionNeutralHandler(context.Background(), &dialect.Request{
					Version: "v63.0",
					Body:    []byte(`{"reportMetadata":{"name":"Pipeline","sourceObject":"Opportunity"}}`),
				}, dialect.Deps{Contracts: contract.Deps{
					Reports: &reportRouteReportsStub{runErr: tt.runErr},
				}})
				if err != nil {
					t.Fatalf("handler error: %v", err)
				}
			})
			assertSFError(t, resp, http.StatusBadRequest, wire.SFCodeInvalidRequest)
			got := mustJSON(t, resp.Body)
			for _, want := range tt.wantBody {
				if !strings.Contains(got, want) {
					t.Fatalf("body = %s, want %q", got, want)
				}
			}
			if strings.Contains(got, reportGenericInternalErrorMessage) {
				t.Fatalf("body = %s, want client validation error not generic internal", got)
			}
			if logs != "" {
				t.Fatalf("logs = %q, want no server-side internal log for client validation", logs)
			}
		})
	}
}

func TestReportFolderAccessDependencyFailsClosedForFolderMoves(t *testing.T) {
	tests := []struct {
		name       string
		invoke     func(*reportRouteRecordsStub) (*dialect.Response, error)
		assertNoIO func(*testing.T, *reportRouteRecordsStub)
	}{
		{
			name: "create",
			invoke: func(records *reportRouteRecordsStub) (*dialect.Response, error) {
				return CreateReportNeutralHandler(context.Background(), &dialect.Request{
					Version: "v63.0",
					User:    &models.UserSession{ID: "005-viewer"},
					Body: []byte(`{
						"reportMetadata": {
							"name": "Pipeline",
							"sourceObject": "Opportunity",
							"folderId": "00lxx0000000099AAA"
						}
					}`),
				}, dialect.Deps{Contracts: contract.Deps{Records: records}})
			},
			assertNoIO: func(t *testing.T, records *reportRouteRecordsStub) {
				t.Helper()
				if records.insertedObject != "" {
					t.Fatalf("inserted object = %q, want no insert", records.insertedObject)
				}
			},
		},
		{
			name: "update",
			invoke: func(records *reportRouteRecordsStub) (*dialect.Response, error) {
				return UpdateReportNeutralHandler(context.Background(), &dialect.Request{
					Version: "v63.0",
					User:    &models.UserSession{ID: "005-viewer"},
					Params:  map[string]string{"reportId": "00Oxx0000000007AAA"},
					Body: []byte(`{
						"reportMetadata": {
							"folderId": "00lxx0000000099AAA"
						}
					}`),
				}, dialect.Deps{Contracts: contract.Deps{Records: records}})
			},
			assertNoIO: func(t *testing.T, records *reportRouteRecordsStub) {
				t.Helper()
				if records.updatedObject != "" {
					t.Fatalf("updated object = %q, want no update", records.updatedObject)
				}
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			records := &reportRouteRecordsStub{}
			var resp *dialect.Response
			logs := captureReportRouteLogs(t, func() {
				var err error
				resp, err = tt.invoke(records)
				if err != nil {
					t.Fatalf("handler error: %v", err)
				}
			})
			assertReportGenericInternalError(t, resp, "folder access")
			if !strings.Contains(logs, "folder access contract unavailable") {
				t.Fatalf("logs = %q, want FolderAccess misconfiguration", logs)
			}
			tt.assertNoIO(t, records)
		})
	}
}

func TestReportWriteConflictsRemainClientErrors(t *testing.T) {
	tests := []struct {
		name    string
		message string
		invoke  func() (*dialect.Response, error)
	}{
		{
			name:    "create duplicate developer name",
			message: "Report already exists with DeveloperName='Pipeline'",
			invoke: func() (*dialect.Response, error) {
				return CreateReportNeutralHandler(context.Background(), &dialect.Request{
					Version: "v63.0",
					Body:    []byte(`{"reportMetadata":{"name":"Pipeline","sourceObject":"Opportunity"}}`),
				}, dialect.Deps{Contracts: contract.Deps{
					Records: &reportRouteRecordsStub{insertErr: statusError{status: http.StatusConflict, message: "Report already exists with DeveloperName='Pipeline'"}},
				}})
			},
		},
		{
			name:    "update duplicate developer name",
			message: "Report already exists with DeveloperName='Pipeline'",
			invoke: func() (*dialect.Response, error) {
				return UpdateReportNeutralHandler(context.Background(), &dialect.Request{
					Version: "v63.0",
					Params:  map[string]string{"reportId": "00Oxx0000000009AAA"},
					Body:    []byte(`{"reportMetadata":{"developerName":"Pipeline"}}`),
				}, dialect.Deps{Contracts: contract.Deps{
					Records: &reportRouteRecordsStub{updateErr: statusError{status: http.StatusConflict, message: "Report already exists with DeveloperName='Pipeline'"}},
				}})
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var resp *dialect.Response
			logs := captureReportRouteLogs(t, func() {
				var err error
				resp, err = tt.invoke()
				if err != nil {
					t.Fatalf("handler error: %v", err)
				}
			})
			assertSFError(t, resp, http.StatusConflict, wire.SFCodeConflict)
			got := mustJSON(t, resp.Body)
			if !strings.Contains(got, tt.message) {
				t.Fatalf("body = %s, want conflict message %q", got, tt.message)
			}
			if logs != "" {
				t.Fatalf("logs = %q, want no server-side internal log for client conflict", logs)
			}
		})
	}
}

type reportRouteRecordsStub struct {
	dashboardRouteRecordsStub
	insertedData  contract.Record
	updatedData   contract.Record
	deletedObject string
	insertErr     error
	updateErr     error
	retrieveErr   error
	deleteErr     error
}

func (s *reportRouteRecordsStub) Insert(_ context.Context, objectName string, data contract.Record, _ *models.UserSession) (contract.Record, error) {
	s.insertedObject = objectName
	s.insertedData = data
	if s.insertErr != nil {
		return nil, s.insertErr
	}
	return s.insertResult, nil
}

func (s *reportRouteRecordsStub) Retrieve(context.Context, string, string, *models.UserSession) (contract.Record, error) {
	if s.retrieveErr != nil {
		return nil, s.retrieveErr
	}
	return s.retrieveResult, nil
}

func (s *reportRouteRecordsStub) Update(_ context.Context, objectName string, _ string, data contract.Record, _ *models.UserSession) error {
	s.updatedObject = objectName
	s.updatedData = data
	if s.updateErr != nil {
		return s.updateErr
	}
	return nil
}

func (s *reportRouteRecordsStub) Delete(_ context.Context, objectName string, _ string, _ *models.UserSession) error {
	s.deletedObject = objectName
	if s.deleteErr != nil {
		return s.deleteErr
	}
	return nil
}

type reportRouteReportsStub struct {
	metadata          *models.ReportDefinition
	result            *models.ReportResult
	runDefinition     *models.ReportDefinition
	runLimit          int
	sourceBaseObjects map[string]string
	sourceErr         error
	metadataErr       error
	runErr            error
}

func (s *reportRouteReportsStub) GetReportMetadata(context.Context, string, *models.UserSession) (*models.ReportDefinition, error) {
	if s.metadataErr != nil {
		return nil, s.metadataErr
	}
	if s.metadata != nil {
		def := *s.metadata
		return &def, nil
	}
	return &models.ReportDefinition{ID: "00O-default", Name: "Pipeline", SourceObject: "Opportunity", SourceRef: "OpportunityReport"}, nil
}

func (s *reportRouteReportsStub) RunReportDefinition(_ context.Context, def *models.ReportDefinition, _ *models.UserSession, _ map[string]interface{}, limit int) (*models.ReportResult, error) {
	s.runLimit = limit
	if def != nil {
		copied := *def
		s.runDefinition = &copied
	}
	if s.runErr != nil {
		return nil, s.runErr
	}
	if s.result != nil {
		result := *s.result
		return &result, nil
	}
	return &models.ReportResult{ReportMetadata: *def}, nil
}

func (s *reportRouteReportsStub) ResolveReportSourceBaseObject(_ context.Context, sourceToken string, _ *models.UserSession) (string, error) {
	if s.sourceErr != nil {
		return "", s.sourceErr
	}
	return s.sourceBaseObjects[sourceToken], nil
}

func assertReportRouteJSON(t *testing.T, gotBody any, wantJSON string) {
	t.Helper()
	assertDashboardRouteBodyJSON(t, gotBody, wantJSON)
	gotBytes, err := json.Marshal(gotBody)
	if err != nil {
		t.Fatalf("marshal body: %v", err)
	}
	for _, forbidden := range []string{models.ObjectTypeMetadataKey, "attributes", "_metadata", "InternalOnly", "internal", "WrongReport", "/old"} {
		if strings.Contains(string(gotBytes), forbidden) {
			t.Fatalf("body leaked neutral carrier key %q: %s", forbidden, gotBytes)
		}
	}
}

func assertReportGenericInternalError(t *testing.T, resp *dialect.Response, forbidden string) {
	t.Helper()
	assertSFError(t, resp, http.StatusInternalServerError, wire.SFCodeInternalError)
	got := mustJSON(t, resp.Body)
	if !strings.Contains(got, reportGenericInternalErrorMessage) {
		t.Fatalf("body = %s, want generic internal message", got)
	}
	if forbidden != "" && strings.Contains(got, forbidden) {
		t.Fatalf("internal error body leaked %q: %s", forbidden, got)
	}
}

func captureReportRouteLogs(t *testing.T, fn func()) string {
	t.Helper()
	var buf bytes.Buffer
	previousWriter := log.Writer()
	previousFlags := log.Flags()
	log.SetOutput(&buf)
	log.SetFlags(0)
	t.Cleanup(func() {
		log.SetOutput(previousWriter)
		log.SetFlags(previousFlags)
	})
	fn()
	return buf.String()
}

type reportRouteInternalError struct {
	msg   string
	cause error
}

func (e reportRouteInternalError) Error() string { return e.msg }

func (e reportRouteInternalError) Unwrap() error { return e.cause }

func (reportRouteInternalError) InternalCondition() bool { return true }
