package rest

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/application/services"
	appErrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
)

type toolingCustomFieldPicklistValue struct {
	FullName string `json:"fullName"`
	Label    string `json:"label"`
	Default  bool   `json:"default"`
}

type toolingCustomFieldValueSetDefinition struct {
	Sorted bool                              `json:"sorted"`
	Value  []toolingCustomFieldPicklistValue `json:"value"`
}

type toolingCustomFieldPicklist struct {
	PicklistValues []toolingCustomFieldPicklistValue `json:"picklistValues"`
	Sorted         bool                              `json:"sorted"`
}

type toolingCustomFieldMetadata struct {
	Label            string                      `json:"label"`
	Type             string                      `json:"type"`
	Required         bool                        `json:"required"`
	IsUnique         bool                        `json:"unique"`
	Description      string                      `json:"description"`
	InlineHelpText   *string                     `json:"inlineHelpText"`
	DefaultValue     *string                     `json:"defaultValue"`
	Length           *int                        `json:"length"`
	Precision        *int                        `json:"precision"`
	Scale            *int                        `json:"scale"`
	ReferenceTo      []string                    `json:"referenceTo"`
	RelationshipName *string                     `json:"relationshipName"`
	Picklist         *toolingCustomFieldPicklist `json:"picklist"`
	ValueSet         *struct {
		ValueSetDefinition *toolingCustomFieldValueSetDefinition `json:"valueSetDefinition"`
	} `json:"valueSet"`
}

type toolingCustomFieldRequest struct {
	FullName         string                      `json:"FullName"`
	TableEnumOrId    string                      `json:"TableEnumOrId"`
	ObjectAPIName    string                      `json:"object_api_name"`
	Name             string                      `json:"name"`
	Label            string                      `json:"label"`
	Type             string                      `json:"type"`
	Required         bool                        `json:"required"`
	IsUnique         bool                        `json:"unique"`
	Description      string                      `json:"description"`
	InlineHelpText   *string                     `json:"inlineHelpText"`
	DefaultValue     *string                     `json:"defaultValue"`
	Length           *int                        `json:"length"`
	Precision        *int                        `json:"precision"`
	Scale            *int                        `json:"scale"`
	ReferenceTo      []string                    `json:"referenceTo"`
	RelationshipName *string                     `json:"relationshipName"`
	Picklist         *toolingCustomFieldPicklist `json:"picklist"`
	ValueSet         *struct {
		ValueSetDefinition *toolingCustomFieldValueSetDefinition `json:"valueSetDefinition"`
	} `json:"valueSet"`
	Metadata toolingCustomFieldMetadata `json:"Metadata"`
}

func normalizeToolingCustomFieldRequest(toolingReq toolingCustomFieldRequest) (string, models.FieldMetadata, error) {
	objectAPIName := toolingReq.TableEnumOrId
	if objectAPIName == "" {
		objectAPIName = toolingReq.ObjectAPIName
	}
	fieldAPIName := toolingReq.Name
	if parts := strings.SplitN(toolingReq.FullName, ".", 2); len(parts) == 2 {
		if objectAPIName == "" {
			objectAPIName = parts[0]
		}
		fieldAPIName = parts[1]
	} else if toolingReq.FullName != "" && fieldAPIName == "" {
		fieldAPIName = toolingReq.FullName
	}

	if objectAPIName == "" {
		return "", models.FieldMetadata{}, appErrors.NewValidationError("TableEnumOrId", "TableEnumOrId or object_api_name is required")
	}
	if fieldAPIName == "" {
		return "", models.FieldMetadata{}, appErrors.NewValidationError("FullName", "FullName or name is required")
	}

	metadata := toolingReq.Metadata
	if metadata.Label == "" {
		metadata.Label = toolingReq.Label
	}
	if metadata.Type == "" {
		metadata.Type = toolingReq.Type
	}
	if !metadata.Required {
		metadata.Required = toolingReq.Required
	}
	if !metadata.IsUnique {
		metadata.IsUnique = toolingReq.IsUnique
	}
	if metadata.Description == "" {
		metadata.Description = toolingReq.Description
	}
	if metadata.InlineHelpText == nil {
		metadata.InlineHelpText = toolingReq.InlineHelpText
	}
	if metadata.DefaultValue == nil {
		metadata.DefaultValue = toolingReq.DefaultValue
	}
	if metadata.Length == nil {
		metadata.Length = toolingReq.Length
	}
	if metadata.Precision == nil {
		metadata.Precision = toolingReq.Precision
	}
	if metadata.Scale == nil {
		metadata.Scale = toolingReq.Scale
	}
	if len(metadata.ReferenceTo) == 0 {
		metadata.ReferenceTo = toolingReq.ReferenceTo
	}
	if metadata.RelationshipName == nil {
		metadata.RelationshipName = toolingReq.RelationshipName
	}
	if metadata.Picklist == nil {
		metadata.Picklist = toolingReq.Picklist
	}
	if metadata.ValueSet == nil {
		metadata.ValueSet = toolingReq.ValueSet
	}

	sfTypeMap := map[string]string{
		"Text":                "string",
		"LongTextArea":        "textarea",
		"Html":                "richtext",
		"Number":              "double",
		"Currency":            "currency",
		"Percent":             "percent",
		"Date":                "date",
		"DateTime":            "datetime",
		"Time":                "time",
		"Checkbox":            "boolean",
		"Email":               "email",
		"Phone":               "phone",
		"Url":                 "url",
		"Picklist":            "picklist",
		"MultiselectPicklist": "multipicklist",
		"Lookup":              "reference",
	}
	internalType := metadata.Type
	if mapped, ok := sfTypeMap[metadata.Type]; ok {
		internalType = mapped
	}

	field := models.FieldMetadata{
		APIName:  fieldAPIName,
		Label:    metadata.Label,
		Type:     models.FieldType(internalType),
		Required: metadata.Required,
		IsUnique: metadata.IsUnique,
		HelpText: metadata.InlineHelpText,
	}
	if field.Label == "" {
		field.Label = fieldAPIName
	}
	if metadata.DefaultValue != nil {
		field.DefaultValue = metadata.DefaultValue
	}
	if metadata.Length != nil {
		field.MaxLength = metadata.Length
	}
	if metadata.Precision != nil {
		field.Precision = metadata.Precision
	}
	if metadata.Scale != nil {
		field.Scale = metadata.Scale
	}
	if len(metadata.ReferenceTo) > 0 {
		field.ReferenceTo = metadata.ReferenceTo
	}
	if metadata.RelationshipName != nil {
		field.RelationshipName = metadata.RelationshipName
	}

	if metadata.ValueSet != nil && metadata.ValueSet.ValueSetDefinition != nil {
		for _, v := range metadata.ValueSet.ValueSetDefinition.Value {
			field.Options = append(field.Options, models.PicklistOption{Value: v.FullName, Label: v.FullName})
		}
	} else if metadata.Picklist != nil {
		for _, v := range metadata.Picklist.PicklistValues {
			field.Options = append(field.Options, models.PicklistOption{Value: v.FullName, Label: v.FullName})
		}
	}

	return objectAPIName, field, nil
}

// ToolingMetadataHandler provides Tooling API sObject shims for CustomObject,
// CustomField, and ProfileLayout, delegating to existing metadata services.
type ToolingMetadataHandler struct {
	svc *services.ServiceManager
}

func NewToolingMetadataHandler(svc *services.ServiceManager) *ToolingMetadataHandler {
	return &ToolingMetadataHandler{svc: svc}
}

// CreateCustomObject handles POST /tooling/sobjects/CustomObject
// The frontend sends Salesforce Tooling API format:
//
//	{
//	  "name": "Account",
//	  "label": "Account",
//	  "labelPlural": "Accounts",
//	  "custom": false,
//	  "description": "..."
//	}
func (h *ToolingMetadataHandler) CreateCustomObject(c *gin.Context) {
	MustGetUser(c)

	// Accept Salesforce Tooling API CustomObject format from the frontend
	var toolingReq struct {
		Name        string  `json:"name"`
		Label       string  `json:"label"`
		LabelPlural string  `json:"labelPlural"`
		Custom      bool    `json:"custom"`
		Description *string `json:"description"`
	}
	if !BindJSON(c, &toolingReq) {
		return
	}

	// Validate required fields
	if toolingReq.Name == "" {
		RespondAppError(c, appErrors.NewValidationError("name", "name is required"))
		return
	}

	// Map Salesforce Tooling API format to internal ObjectMetadata
	objectAPIName := toolingReq.Name
	label := toolingReq.Label
	if label == "" {
		label = toolingReq.Name
	}
	pluralLabel := toolingReq.LabelPlural
	if pluralLabel == "" {
		pluralLabel = toolingReq.Name + "s"
	}
	description := ""
	if toolingReq.Description != nil {
		description = *toolingReq.Description
	}

	// Try to get existing schema - if it exists, return success (idempotent behavior)
	if existingSchema := h.svc.Metadata.GetSchema(c.Request.Context(), objectAPIName); existingSchema != nil {
		c.JSON(http.StatusOK, gin.H{
			"id":      objectAPIName,
			"success": true,
			"errors":  []interface{}{},
		})
		return
	}

	// Create the schema using the metadata service
	schema := &models.ObjectMetadata{
		APIName:      objectAPIName,
		Label:        label,
		PluralLabel:  pluralLabel,
		Description:  &description,
		IsCustom:     toolingReq.Custom,
		SharingModel: models.SharingModel("Private"),
		Searchable:   true,
	}

	if err := h.svc.Metadata.CreateSchema(c.Request.Context(), schema); err != nil {
		// Check if it's a conflict error (object already exists)
		if appErrors.IsConflict(err) {
			c.JSON(http.StatusOK, gin.H{
				"id":      objectAPIName,
				"success": true,
				"errors":  []interface{}{},
			})
			return
		}
		RespondAppError(c, err)
		return
	}
	if err := provisionCreatedSchemaPermissions(c.Request.Context(), h.svc, schema); err != nil {
		RespondAppError(c, err)
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"id":      objectAPIName,
		"success": true,
		"errors":  []interface{}{},
	})
}

// UpdateCustomObject handles PATCH /tooling/sobjects/CustomObject/:id
// The :id param is the object's API name (e.g. "account", "my_custom__c").
// The frontend sends PascalCase keys (Label, PluralLabel, Description) matching
// the Salesforce Tooling API convention.
func (h *ToolingMetadataHandler) UpdateCustomObject(c *gin.Context) {
	MustGetUser(c)

	apiName := c.Param("id")

	// Accept PascalCase keys from the frontend (Salesforce Tooling API convention)
	var toolingReq struct {
		Label       string  `json:"Label"`
		PluralLabel string  `json:"PluralLabel"`
		Description *string `json:"Description"`
	}
	if !BindJSON(c, &toolingReq) {
		return
	}

	// Map PascalCase tooling payload to internal ObjectMetadata
	var updates models.ObjectMetadata
	updates.Label = toolingReq.Label
	updates.PluralLabel = toolingReq.PluralLabel
	updates.Description = toolingReq.Description

	if err := h.svc.Metadata.UpdateSchema(c.Request.Context(), apiName, &updates); err != nil {
		RespondAppError(c, err)
		return
	}

	c.Status(http.StatusNoContent)
}

// CreateCustomField handles POST /tooling/sobjects/CustomField
// The frontend sends Salesforce Tooling API format:
//
//	{
//	  "FullName": "ObjectName.field__c",
//	  "Metadata": { "label": "...", "type": "...", ... },
//	  "TableEnumOrId": "ObjectName"
//	}
func (h *ToolingMetadataHandler) CreateCustomField(c *gin.Context) {
	MustGetUser(c)

	var toolingReq toolingCustomFieldRequest
	if !BindJSON(c, &toolingReq) {
		return
	}

	objectAPIName, field, err := normalizeToolingCustomFieldRequest(toolingReq)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	if err := h.svc.Metadata.CreateField(c.Request.Context(), objectAPIName, &field); err != nil {
		RespondAppError(c, err)
		return
	}
	if err := finalizeCreatedFields(c.Request.Context(), h.svc, objectAPIName, []*models.FieldMetadata{&field}); err != nil {
		RespondAppError(c, err)
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"id":      field.APIName,
		"success": true,
		"errors":  []interface{}{},
	})
}

// CreateProfileLayout handles POST /tooling/sobjects/ProfileLayout
// The frontend sends { ProfileId, ObjectApiName, layout_id } — note layout_id
// is snake_case while the others are PascalCase (matching Salesforce conventions).
func (h *ToolingMetadataHandler) CreateProfileLayout(c *gin.Context) {
	MustGetUser(c)

	var req struct {
		ProfileID     string `json:"ProfileId"`
		ObjectAPIName string `json:"ObjectApiName"`
		LayoutID      string `json:"layout_id"`
	}
	if !BindJSON(c, &req) {
		return
	}

	if req.ProfileID == "" || req.ObjectAPIName == "" || req.LayoutID == "" {
		RespondAppError(c, appErrors.NewValidationError("body", "ProfileId, ObjectApiName, and layout_id are required"))
		return
	}

	objectAPIName := req.ObjectAPIName
	if err := h.svc.Metadata.AssignLayoutToProfile(c.Request.Context(), req.ProfileID, objectAPIName, req.LayoutID); err != nil {
		RespondAppError(c, err)
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"id":      req.ProfileID + "-" + objectAPIName + "-" + req.LayoutID,
		"success": true,
		"errors":  []interface{}{},
	})
}
