package restheadspec

import (
	"encoding/json"
	"testing"

	"github.com/bitechdev/ResolveSpec/pkg/common"
	"github.com/bitechdev/ResolveSpec/pkg/spectypes"
)

// detailTestModel is a simple model with gorm column/type tags for detail format tests.
type detailTestModel struct {
	ID          int64   `bun:"rid,pk" gorm:"column:rid;primaryKey" json:"rid"`
	Name        string  `bun:"name" gorm:"column:name;type:citext" json:"name"`
	Description *string `bun:"description" gorm:"column:description;type:text;nullable" json:"description"`
	Score       float64 `bun:"score" gorm:"column:score;type:numeric" json:"score"`
	Active      bool    `bun:"active" gorm:"column:active;type:boolean;not null" json:"active"`
}

func TestSendFormattedResponse_DetailFormat(t *testing.T) {
	handler := &Handler{}

	name := "hello"
	items := []*detailTestModel{
		{ID: 1, Name: "first", Description: &name, Score: 1.5, Active: true},
		{ID: 2, Name: "second", Description: nil, Score: 2.0, Active: false},
	}
	metadata := &common.Metadata{
		Total:    36,
		Count:    2,
		Filtered: 36,
		Limit:    10,
		Offset:   0,
	}
	options := ExtendedRequestOptions{
		ResponseFormat: "detail",
	}

	mockWriter := &MockTestResponseWriter{headers: make(map[string]string)}
	handler.sendFormattedResponse(mockWriter, items, metadata, "myschema.myentity", detailTestModel{}, options)

	if mockWriter.statusCode != 200 {
		t.Fatalf("expected status 200, got %d", mockWriter.statusCode)
	}

	body, err := json.Marshal(mockWriter.body)
	if err != nil {
		t.Fatalf("failed to marshal body: %v", err)
	}

	var resp map[string]json.RawMessage
	if err := json.Unmarshal(body, &resp); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}

	t.Run("top-level keys", func(t *testing.T) {
		for _, key := range []string{"count", "fields", "items", "tablename", "tableprefix", "total"} {
			if _, ok := resp[key]; !ok {
				t.Errorf("missing key %q in detail response", key)
			}
		}
	})

	t.Run("count and total are string", func(t *testing.T) {
		var count, total string
		if err := json.Unmarshal(resp["count"], &count); err != nil {
			t.Errorf("count is not a string: %v", err)
		}
		if err := json.Unmarshal(resp["total"], &total); err != nil {
			t.Errorf("total is not a string: %v", err)
		}
		if count != "2" {
			t.Errorf("expected count %q, got %q", "2", count)
		}
		if total != "36" {
			t.Errorf("expected total %q, got %q", "36", total)
		}
	})

	t.Run("tablename and tableprefix", func(t *testing.T) {
		var tablename, tableprefix string
		json.Unmarshal(resp["tablename"], &tablename)
		json.Unmarshal(resp["tableprefix"], &tableprefix)
		if tablename != "myschema.myentity" {
			t.Errorf("expected tablename %q, got %q", "myschema.myentity", tablename)
		}
		if tableprefix != "myentity" {
			t.Errorf("expected tableprefix %q, got %q", "myentity", tableprefix)
		}
	})

	t.Run("items contains data", func(t *testing.T) {
		var itemSlice []map[string]interface{}
		if err := json.Unmarshal(resp["items"], &itemSlice); err != nil {
			t.Fatalf("items is not an array: %v", err)
		}
		if len(itemSlice) != 2 {
			t.Errorf("expected 2 items, got %d", len(itemSlice))
		}
	})

	t.Run("fields contains column metadata", func(t *testing.T) {
		var fields []map[string]interface{}
		if err := json.Unmarshal(resp["fields"], &fields); err != nil {
			t.Fatalf("fields is not an array: %v", err)
		}
		if len(fields) == 0 {
			t.Fatal("expected fields to be non-empty")
		}

		bySQL := make(map[string]map[string]interface{}, len(fields))
		for _, f := range fields {
			if sqlname, ok := f["sqlname"].(string); ok {
				bySQL[sqlname] = f
			}
		}

		// Check required field keys are present
		for _, f := range fields {
			for _, key := range []string{"name", "datatype", "sqlname", "sqldatatype", "sqlkey", "nullable"} {
				if _, ok := f[key]; !ok {
					t.Errorf("field %v missing key %q", f, key)
				}
			}
		}

		// Validate specific columns
		if col, ok := bySQL["rid"]; ok {
			if col["sqlkey"] != "primary_key" {
				t.Errorf("rid: expected sqlkey %q, got %v", "primary_key", col["sqlkey"])
			}
		} else {
			t.Error("expected column 'rid' in fields")
		}

		if col, ok := bySQL["name"]; ok {
			if col["sqldatatype"] != "citext" {
				t.Errorf("name: expected sqldatatype %q, got %v", "citext", col["sqldatatype"])
			}
			if col["nullable"] != false {
				t.Errorf("name: expected nullable false, got %v", col["nullable"])
			}
		} else {
			t.Error("expected column 'name' in fields")
		}

		if col, ok := bySQL["description"]; ok {
			if col["sqldatatype"] != "text" {
				t.Errorf("description: expected sqldatatype %q, got %v", "text", col["sqldatatype"])
			}
			if col["nullable"] != true {
				t.Errorf("description: expected nullable true, got %v", col["nullable"])
			}
		} else {
			t.Error("expected column 'description' in fields")
		}
	})
}

func TestSendFormattedResponse_DetailFormat_EmptyItems(t *testing.T) {
	handler := &Handler{}
	metadata := &common.Metadata{Total: 0, Count: 0, Filtered: 0}
	options := ExtendedRequestOptions{ResponseFormat: "detail"}

	mockWriter := &MockTestResponseWriter{headers: make(map[string]string)}
	handler.sendFormattedResponse(mockWriter, []*detailTestModel{}, metadata, "s.t", detailTestModel{}, options)

	body, _ := json.Marshal(mockWriter.body)
	var resp map[string]json.RawMessage
	json.Unmarshal(body, &resp)

	var count, total string
	json.Unmarshal(resp["count"], &count)
	json.Unmarshal(resp["total"], &total)

	if count != "0" || total != "0" {
		t.Errorf("expected count/total both %q, got count=%q total=%q", "0", count, total)
	}

	var fields []interface{}
	json.Unmarshal(resp["fields"], &fields)
	if len(fields) == 0 {
		t.Error("fields should still list column metadata even when items is empty")
	}
}

func TestBuildDetailFields_SkipsRelations(t *testing.T) {
	type child struct {
		ID int64 `bun:"id,pk" gorm:"column:id;primaryKey" json:"id"`
	}
	type parent struct {
		ID       int64   `bun:"id,pk" gorm:"column:id;primaryKey" json:"id"`
		Name     string  `bun:"name" gorm:"column:name" json:"name"`
		Children []child `bun:"rel:has-many" json:"children"`
		Child    *child  `bun:"rel:has-one" json:"child"`
	}

	handler := &Handler{}
	fields := handler.buildDetailFields(parent{})

	for _, f := range fields {
		if f.SQLName == "children" || f.SQLName == "child" {
			t.Errorf("relation field %q should not appear in detail fields", f.SQLName)
		}
	}

	if len(fields) != 2 {
		t.Errorf("expected 2 scalar fields (id, name), got %d", len(fields))
	}
}

func TestBuildDetailFields_UnwrapsSQLTypes(t *testing.T) {
	type model struct {
		Name      spectypes.SqlString    `gorm:"column:name" json:"name"`
		CreatedAt spectypes.SqlTimeStamp `gorm:"column:created_at" json:"created_at"`
		Metadata  spectypes.SqlJSONB     `gorm:"column:metadata;type:jsonb" json:"metadata"`
	}

	fields := (&Handler{}).buildDetailFields(model{})
	byName := make(map[string]string, len(fields))
	for _, field := range fields {
		byName[field.Name] = field.DataType
		if !field.Nullable {
			t.Errorf("%q: expected SQL wrapper to be nullable", field.Name)
		}
	}

	for name, want := range map[string]string{
		"name":       "string",
		"created_at": "unknown",
		"metadata":   "unknown",
	} {
		if got := byName[name]; got != want {
			t.Errorf("%q: expected type %q, got %q", name, want, got)
		}
	}
}
