package rest

import (
	"fmt"
	"net/http"
	"strings"

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

type UIHandler struct {
	svc *services.ServiceManager
}

func NewUIHandler(svc *services.ServiceManager) *UIHandler {
	return &UIHandler{svc: svc}
}

// ── Salesforce UI API Wire Types (/ui-api/apps) ──

// sfUIAppNavItem mirrors a Salesforce UI API nav item shape.
type sfUIAppNavItem struct {
	ID            string                `json:"id"`
	DeveloperName string                `json:"developerName"`
	Label         string                `json:"label"`
	ObjectApiName string                `json:"objectApiName,omitempty"`
	IconUrl       string                `json:"iconUrl,omitempty"`
	StandardType  string                `json:"standardType,omitempty"`
	ItemType      string                `json:"itemType,omitempty"`
	PageReference *sfUIAppPageReference `json:"pageReference,omitempty"`
}

// sfUIAppPageReference mirrors the Salesforce page reference type.
type sfUIAppPageReference struct {
	Type       string            `json:"type"`
	Attributes map[string]string `json:"attributes,omitempty"`
}

// sfUIApp mirrors the Salesforce /ui-api/apps response app shape.
type sfUIApp struct {
	AppId                        string               `json:"appId"`
	DeveloperName                string               `json:"developerName,omitempty"`
	Description                  string               `json:"description,omitempty"`
	FormFactors                  []string             `json:"formFactors"`
	HeaderColor                  string               `json:"headerColor,omitempty"`
	IconUrl                      string               `json:"iconUrl,omitempty"`
	IsNavPersonalizationDisabled bool                 `json:"isNavPersonalizationDisabled"`
	Label                        string               `json:"label"`
	LogoUrl                      string               `json:"logoUrl,omitempty"`
	NavItems                     []sfUIAppNavItem     `json:"navItems"`
	Selected                     bool                 `json:"selected"`
	UtilityItems                 []models.UtilityItem `json:"utilityItems,omitempty"`
	AssignedProfiles             []string             `json:"assignedProfiles,omitempty"`
}

// sfUIAppsResponse mirrors the Salesforce /ui-api/apps top-level response.
// Salesforce returns apps as a MAP keyed by appId, NOT an array.
type sfUIAppsResponse struct {
	Apps map[string]sfUIApp `json:"apps"`
}

// ── Wire Type ↔ Internal Model Conversions ──

// toSFUIApp converts an internal AppConfig to the Salesforce UI API wire format.
// This is the ONLY place protocol translation happens — at the REST boundary.
func toSFUIApp(app *models.AppConfig) sfUIApp {
	navItems := make([]sfUIAppNavItem, 0, len(app.NavigationItems))
	for _, nav := range app.NavigationItems {
		item := sfUIAppNavItem{
			ID:            nav.ID,
			DeveloperName: nav.ID,
			Label:         nav.Label,
			IconUrl:       nav.Icon,
			ItemType:      nav.Type,
		}
		switch nav.Type {
		case constants.NavigationTypeObject:
			item.ObjectApiName = nav.ObjectAPIName
		case constants.NavigationTypeWeb:
			item.PageReference = &sfUIAppPageReference{
				Type:       "standard__webPage",
				Attributes: map[string]string{"url": nav.PageURL},
			}
		case constants.NavigationTypeDashboard:
			item.PageReference = &sfUIAppPageReference{
				Type:       "standard__dashboard",
				Attributes: map[string]string{"dashboardId": nav.DashboardID},
			}
		case constants.NavigationTypePage:
			// Standard page type — e.g. Home
			item.StandardType = nav.ObjectAPIName // reuse for standard type name
		}
		navItems = append(navItems, item)
	}
	return sfUIApp{
		AppId:            app.ID,
		DeveloperName:    app.ID,
		Label:            app.Label,
		Description:      app.Description,
		FormFactors:      []string{"Large"},
		HeaderColor:      app.Color,
		IconUrl:          app.Icon,
		NavItems:         navItems,
		Selected:         app.IsDefault,
		UtilityItems:     app.UtilityItems,
		AssignedProfiles: app.AssignedProfiles,
	}
}

// ── Salesforce Tooling API Wire Types (POST/PATCH /tooling/sobjects/CustomApplication) ──

// toolingCustomApplicationMetadata mirrors the Salesforce Tooling API Metadata block
// for the CustomApplication object. This is what the frontend sends inside the
// "Metadata" key when creating/updating apps.
type toolingCustomApplicationMetadata struct {
	Label       string   `json:"label"`
	Description string   `json:"description,omitempty"`
	NavType     string   `json:"navType,omitempty"`
	UiType      string   `json:"uiType,omitempty"`
	FormFactors []string `json:"formFactors,omitempty"`
	Tabs        []string `json:"tabs,omitempty"`
}

// toolingCustomApplicationRequest mirrors the Salesforce Tooling API request format
// for creating/updating CustomApplication records.
// The frontend sends: { "FullName": "dev_name", "Metadata": { "label": "...", "tabs": [...] } }
type toolingCustomApplicationRequest struct {
	FullName string                           `json:"FullName"`
	Metadata toolingCustomApplicationMetadata `json:"Metadata"`
}

// toAppConfig converts a Salesforce Tooling API request to the internal AppConfig model.
// Conversion rules:
//   - FullName → AppConfig.ID (the developer name / unique identifier)
//   - Metadata.label → AppConfig.Label
//   - Metadata.description → AppConfig.Description
//   - Metadata.tabs → AppConfig.NavigationItems via tab name parsing:
//   - "standard-Account" → { Type: "object", ObjectAPIName: "Account" }
//   - "Custom_Obj__c"    → { Type: "object", ObjectAPIName: "Custom_Obj__c" }
//   - Other IDs          → { Type: "page", ID: tabName }
func (r *toolingCustomApplicationRequest) toAppConfig() *models.AppConfig {
	app := &models.AppConfig{
		ID:          r.FullName,
		Label:       r.Metadata.Label,
		Description: r.Metadata.Description,
	}

	// Convert tab strings to NavigationItem models
	if r.Metadata.Tabs != nil {
		navItems := make([]models.NavigationItem, 0, len(r.Metadata.Tabs))
		for i, tab := range r.Metadata.Tabs {
			item := models.NavigationItem{
				ID: fmt.Sprintf("nav-%d-%s", i, tab),
			}

			if strings.HasPrefix(tab, "standard-") {
				// Standard object: "standard-Account" → objectApiName "Account"
				objectName := strings.TrimPrefix(tab, "standard-")
				item.Type = constants.NavigationTypeObject
				item.ObjectAPIName = objectName
				item.Label = objectName
			} else if strings.HasSuffix(tab, "__c") {
				// Custom object: "Custom_Obj__c"
				item.Type = constants.NavigationTypeObject
				item.ObjectAPIName = tab
				item.Label = tab
			} else {
				// Other tab types (standard pages, dashboard refs, etc.)
				item.Type = constants.NavigationTypePage
				item.Label = tab
			}

			navItems = append(navItems, item)
		}
		app.NavigationItems = navItems
	}

	return app
}

// enrichNavLabelsFromSchemas replaces raw API-name labels on object navigation
// items with human-readable PluralLabel (or Label) from object schemas.
// Uses case-insensitive lookup to handle Salesforce's lowercase tab identifiers.
func enrichNavLabelsFromSchemas(app *models.AppConfig, schemas []*models.ObjectMetadata) {
	if len(app.NavigationItems) == 0 {
		return
	}

	labelMap := make(map[string]*models.ObjectMetadata, len(schemas))
	for _, s := range schemas {
		labelMap[strings.ToLower(s.APIName)] = s
	}

	for i := range app.NavigationItems {
		item := &app.NavigationItems[i]
		if item.Type != constants.NavigationTypeObject || item.ObjectAPIName == "" {
			continue
		}
		schema, ok := labelMap[strings.ToLower(item.ObjectAPIName)]
		if !ok {
			continue
		}
		if schema.PluralLabel != "" {
			item.Label = schema.PluralLabel
		} else if schema.Label != "" {
			item.Label = schema.Label
		}
	}
}

// ── App Handlers ──

// GetAppsUIAPI handles GET /services/data/v63.0/ui-api/apps
// Returns apps in the Salesforce UI API wire format.
// Salesforce returns apps as a MAP keyed by appId.
func (h *UIHandler) GetAppsUIAPI(c *gin.Context) {
	apps := h.svc.Metadata.GetApps(c.Request.Context())
	schemas := h.svc.Metadata.GetSchemas(c.Request.Context())

	for _, app := range apps {
		enrichNavLabelsFromSchemas(app, schemas)
	}

	sfApps := make(map[string]sfUIApp, len(apps))
	for _, app := range apps {
		sf := toSFUIApp(app)
		sfApps[sf.AppId] = sf
	}
	c.JSON(http.StatusOK, sfUIAppsResponse{Apps: sfApps})
}

// GetApps handles GET /services/data/v63.0/tooling/sobjects/CustomApplication
// Salesforce Tooling API query format.
func (h *UIHandler) GetApps(c *gin.Context) {
	apps := h.svc.Metadata.GetApps(c.Request.Context())
	sfApps := make([]sfUIApp, 0, len(apps))
	for _, app := range apps {
		sfApps = append(sfApps, toSFUIApp(app))
	}
	c.JSON(http.StatusOK, gin.H{
		"records":   sfApps,
		"done":      true,
		"totalSize": len(sfApps),
	})
}

// GetAppByID handles GET /services/data/v63.0/tooling/sobjects/CustomApplication/:id
// Returns a single app in Salesforce Tooling API wire format with Metadata.tabs.
// Used by the frontend's Composite Batch enrichment in getToolingSupplementaryApps.
func (h *UIHandler) GetAppByID(c *gin.Context) {
	id := c.Param("id")
	app := h.svc.Metadata.GetApp(c.Request.Context(), id)
	if app == nil {
		RespondAppError(c, apierr.NotFound("Application not found"))
		return
	}

	// Reverse-map NavigationItems → Tooling tab identifier strings
	tabs := make([]string, 0, len(app.NavigationItems))
	for _, nav := range app.NavigationItems {
		switch {
		case nav.Type == constants.NavigationTypeObject && strings.HasSuffix(nav.ObjectAPIName, "__c"):
			tabs = append(tabs, nav.ObjectAPIName)
		case nav.Type == constants.NavigationTypeObject:
			tabs = append(tabs, "standard-"+strings.ToLower(nav.ObjectAPIName))
		default:
			tabs = append(tabs, nav.ID)
		}
	}

	c.JSON(http.StatusOK, gin.H{
		"Id":       app.ID,
		"FullName": app.ID,
		"Metadata": gin.H{
			"label":       app.Label,
			"description": app.Description,
			"tabs":        tabs,
		},
	})
}

// CreateApp handles POST /services/data/v63.0/tooling/sobjects/CustomApplication
// Accepts Salesforce Tooling API wire format: { FullName, Metadata: { label, tabs, ... } }
func (h *UIHandler) CreateApp(c *gin.Context) {
	var req toolingCustomApplicationRequest
	if !BindJSON(c, &req) {
		return
	}

	app := req.toAppConfig()

	// Set default values
	if app.NavigationItems == nil {
		app.NavigationItems = []models.NavigationItem{}
	}

	// Enrich nav labels before persisting so DB stores human-readable labels
	schemas := h.svc.Metadata.GetSchemas(c.Request.Context())
	enrichNavLabelsFromSchemas(app, schemas)

	if err := h.svc.Metadata.CreateApp(c.Request.Context(), app); err != nil {
		RespondAppError(c, err)
		return
	}

	// Salesforce Tooling API response format for created records
	c.JSON(http.StatusCreated, gin.H{
		"id":      app.ID,
		"success": true,
		"errors":  []interface{}{},
	})
}

// UpdateApp handles PATCH /services/data/v63.0/tooling/sobjects/CustomApplication/:id
// Accepts Salesforce Tooling API wire format: { FullName, Metadata: { label, tabs, ... } }
func (h *UIHandler) UpdateApp(c *gin.Context) {
	id := c.Param("id")
	var req toolingCustomApplicationRequest
	if !BindJSON(c, &req) {
		return
	}

	updates := req.toAppConfig()
	updates.ID = id

	// Enrich nav labels before persisting so DB stores human-readable labels
	if updates.NavigationItems != nil {
		schemas := h.svc.Metadata.GetSchemas(c.Request.Context())
		enrichNavLabelsFromSchemas(updates, schemas)
	}

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

	// Salesforce returns 204 No Content on successful PATCH
	c.Status(http.StatusNoContent)
}

// DeleteApp handles DELETE /services/data/v63.0/tooling/sobjects/CustomApplication/:id
func (h *UIHandler) DeleteApp(c *gin.Context) {
	id := c.Param("id")
	if err := h.svc.Metadata.DeleteApp(c.Request.Context(), id); err != nil {
		RespondAppError(c, err)
		return
	}
	// Salesforce returns 204 No Content on successful DELETE
	c.Status(http.StatusNoContent)
}
