package uiapi

import (
	"context"
	"fmt"

	apperrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/backend/pkg/utils"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

// CreateObjectWithApp creates a new object and automatically adds it to an app's navigation.
// This orchestrates Schema Creation -> Permission Provisioning -> App Update.
func (s *Service) CreateObjectWithApp(ctx context.Context, appID string, schema *models.ObjectMetadata) error {
	// 1. Set AppID on schema
	schema.AppID = &appID

	// 2. Create the Schema (delegated to MetadataService)
	if err := s.metadataSvc.CreateSchema(ctx, schema); err != nil {
		return err
	}

	// 3. Provision object + field permissions before returning success. If this
	// fails, roll the schema back so callers do not observe a half-created object.
	if s.permSvc != nil {
		if err := s.permSvc.SeedPermissionsForObject(ctx, schema.APIName); err != nil {
			if rollbackErr := s.metadataSvc.DeleteSchema(ctx, schema.APIName); rollbackErr != nil {
				log.Error().Err(rollbackErr).Str("object", schema.APIName).
					Msg("CreateObjectWithApp: failed to roll back schema after permission provisioning error")
			}
			return apperrors.NewInternalError("failed to provision permissions for new object", err)
		}
		s.permSvc.InvalidateCache()
	} else {
		log.Warn().Str("object", schema.APIName).Msg("PermissionService not available during object creation")
	}

	// 4. Add to App Navigation
	// We use existing public methods which handle locking internally
	app := s.metadataSvc.GetApp(ctx, appID)
	if app == nil {
		// Just log warning if app not found, main object creation succeeded
		return nil
	}

	// Create navigation item
	newItem := models.NavigationItem{
		ID:            fmt.Sprintf("nav-%s-%s", schema.APIName, utils.GenerateID()[:8]),
		Type:          constants.NavigationTypeObject,
		ObjectAPIName: schema.APIName,
		Label:         schema.PluralLabel,
		Icon:          schema.Icon,
	}

	// Append to items
	if app.NavigationItems == nil {
		app.NavigationItems = []models.NavigationItem{}
	}
	app.NavigationItems = append(app.NavigationItems, newItem)

	// Update App with new navigation Item
	if err := s.metadataSvc.UpdateApp(ctx, appID, app); err != nil {
		if rollbackErr := s.metadataSvc.DeleteSchema(ctx, schema.APIName); rollbackErr != nil {
			log.Error().Err(rollbackErr).Str("object", schema.APIName).
				Msg("CreateObjectWithApp: failed to roll back schema after app update error")
		}
		return err
	}
	return nil
}

// AddAppNavigationItemTx adds a new navigation item to an existing app within a transaction
func (s *Service) AddAppNavigationItemTx(ctx context.Context, tx interface{}, appID string, item models.NavigationItem) error {
	// Get current state from DB within Tx for consistency
	app, err := s.metadataSvc.GetAppWithTx(ctx, tx, appID)
	if err != nil {
		return apperrors.NewInternalError("failed to get app", err)
	}
	if app == nil {
		return apperrors.NewNotFoundError("app", appID)
	}

	// Appending to the slice in the new struct
	app.NavigationItems = append(app.NavigationItems, item)

	return s.metadataSvc.UpdateAppTx(ctx, tx, appID, app)
}

// RemoveObjectFromAllAppsTx removes an object from all app navigation items within a transaction
// This is called when an object is deleted to maintain referential integrity
func (s *Service) RemoveObjectFromAllAppsTx(ctx context.Context, tx interface{}, objectAPIName string) error {
	apps := s.metadataSvc.GetApps(ctx)

	for _, app := range apps {
		updated := false
		newItems := []models.NavigationItem{}

		for _, item := range app.NavigationItems {
			// Skip navigation items that point to the deleted object
			if item.Type == constants.NavigationTypeObject && item.ObjectAPIName == objectAPIName {
				updated = true
				continue
			}
			newItems = append(newItems, item)
		}

		// If this app had the object, update it
		if updated {
			app.NavigationItems = newItems
			if err := s.metadataSvc.UpdateAppTx(ctx, tx, app.ID, app); err != nil {
				return err
			}
		}
	}

	return nil
}

// AddAppNavigationItem adds a new navigation item to an existing app
func (s *Service) AddAppNavigationItem(ctx context.Context, appID string, item models.NavigationItem) error {
	app := s.metadataSvc.GetApp(ctx, appID)
	if app == nil {
		return apperrors.NewNotFoundError("app", appID)
	}

	app.NavigationItems = append(app.NavigationItems, item)
	return s.metadataSvc.UpdateApp(ctx, appID, app)
}
