package metadata

import (
	"context"
	"database/sql"
	"errors"
	"strings"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/utils"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

const listViewVisibilityPrivate = "Private"

// ListViewRepository is the persistence boundary used by ListViewService. It is
// intentionally narrower than the full metadata repository: authorization lives
// here, while the repository only reads and writes rows.
type ListViewRepository interface {
	GetPrivateListViews(ctx context.Context, objectAPIName string, ownerID string) ([]*models.ListView, error)
	GetPrivateListView(ctx context.Context, objectAPIName string, id string, ownerID string) (*models.ListView, error)
	CreateListView(ctx context.Context, lv *models.ListView) error
	UpdateListViewPatch(ctx context.Context, id string, ownerID string, patch ListViewPatch) error
	DeletePrivateListView(ctx context.Context, id string, ownerID string) error
	GetListView(ctx context.Context, id string) (*models.ListView, error)
	GetListViewForUpdate(ctx context.Context, id string) (*models.ListView, error)
	GetListViewByDeveloperName(ctx context.Context, objectAPIName, developerName string) (*models.ListView, error)
	GetListViewByDeveloperNameForUpdate(ctx context.Context, objectAPIName, developerName string) (*models.ListView, error)
}

// ListViewPermissionChecker is the permission surface required for private
// list-view writes.
type ListViewPermissionChecker interface {
	HasPermission(ctx context.Context, user *models.UserSession, apiName string) bool
}

// ListViewPatch preserves PATCH presence semantics.
type ListViewPatch = models.ListViewPatch

// ListViewService is the caller-aware authority for user-authored list-view
// writes. Phase 1 deliberately supports Private rows only.
type ListViewService struct {
	repo        ListViewRepository
	permissions ListViewPermissionChecker
}

func NewListViewService(repo ListViewRepository, permissions ListViewPermissionChecker) *ListViewService {
	return &ListViewService{repo: repo, permissions: permissions}
}

// ListPrivate returns only private views owned by the authenticated caller.
// Object read permission is enforced by the Runtime boundary before this
// repository-backed state read; this service owns the non-disclosing ownership
// predicate itself so callers cannot accidentally broaden it.
func (s *ListViewService) ListPrivate(ctx context.Context, user *models.UserSession, objectAPIName string) ([]*models.ListView, error) {
	if user == nil || strings.TrimSpace(user.ID) == "" {
		return nil, apperrors.NewUnauthorizedError("private list-view reads require an authenticated user")
	}
	if s == nil || s.repo == nil {
		return nil, apperrors.NewInternalError("list view repository unavailable", nil)
	}
	objectAPIName = strings.TrimSpace(objectAPIName)
	if objectAPIName == "" {
		return nil, apperrors.NewValidationError("ObjectApiName", "object API name is required")
	}
	views, err := s.repo.GetPrivateListViews(ctx, objectAPIName, strings.TrimSpace(user.ID))
	if err != nil {
		return nil, apperrors.NewInternalError("failed to list private list views", err)
	}
	result := make([]*models.ListView, 0, len(views))
	for _, view := range views {
		if authorizeExistingPrivateListView(view, user.ID) != nil || strings.TrimSpace(view.ObjectAPIName) != objectAPIName {
			return nil, apperrors.NewInternalError("private list view repository returned an unauthorized row", nil)
		}
		result = append(result, cloneListView(view))
	}
	return result, nil
}

// DescribePrivate intentionally maps an absent row, wrong object, and wrong
// owner to the same not-found result in the repository predicate.
func (s *ListViewService) DescribePrivate(ctx context.Context, user *models.UserSession, objectAPIName string, id string) (*models.ListView, error) {
	if user == nil || strings.TrimSpace(user.ID) == "" {
		return nil, apperrors.NewUnauthorizedError("private list-view reads require an authenticated user")
	}
	if s == nil || s.repo == nil {
		return nil, apperrors.NewInternalError("list view repository unavailable", nil)
	}
	objectAPIName = strings.TrimSpace(objectAPIName)
	id = strings.TrimSpace(id)
	if objectAPIName == "" || id == "" {
		return nil, apperrors.NewValidationError("Id", "object API name and list view ID are required")
	}
	view, err := s.repo.GetPrivateListView(ctx, objectAPIName, id, strings.TrimSpace(user.ID))
	if err != nil {
		return nil, listViewReadError(err, id)
	}
	if authorizeExistingPrivateListView(view, user.ID) != nil || strings.TrimSpace(view.ObjectAPIName) != objectAPIName {
		return nil, apperrors.NewNotFoundError("list view", id)
	}
	return cloneListView(view), nil
}

func (s *ListViewService) CreatePrivate(ctx context.Context, user *models.UserSession, input *models.ListView) (*models.ListView, error) {
	return s.createPrivate(ctx, user, input, GenerateID())
}

// CreatePrivateWithID creates a private list view with a service-generated ID
// supplied by the records lifecycle adapter. The ID must still be a UUID so
// callers cannot use this seam to persist reserved or human-authored IDs.
func (s *ListViewService) CreatePrivateWithID(ctx context.Context, user *models.UserSession, input *models.ListView, id string) (*models.ListView, error) {
	id = strings.TrimSpace(id)
	if id == "" {
		return nil, apperrors.NewValidationError("Id", "list view ID is required")
	}
	if !utils.IsValidUUID(id) {
		return nil, apperrors.NewValidationError("Id", "list view ID must be a generated UUID")
	}
	return s.createPrivate(ctx, user, input, id)
}

func (s *ListViewService) ValidatePrivateWrite(ctx context.Context, user *models.UserSession) error {
	if err := s.requirePrivateWrite(ctx, user); err != nil {
		return err
	}
	if s == nil || s.repo == nil {
		return apperrors.NewInternalError("list view repository unavailable", nil)
	}
	return nil
}

func (s *ListViewService) createPrivate(ctx context.Context, user *models.UserSession, input *models.ListView, id string) (*models.ListView, error) {
	if err := s.requirePrivateWrite(ctx, user); err != nil {
		return nil, err
	}
	if s == nil || s.repo == nil {
		return nil, apperrors.NewInternalError("list view repository unavailable", nil)
	}
	if input == nil {
		return nil, apperrors.NewValidationError("listView", "list view is required")
	}
	objectAPIName := strings.TrimSpace(input.ObjectAPIName)
	if objectAPIName == "" {
		return nil, apperrors.NewValidationError("ObjectApiName", "object API name is required")
	}
	label := strings.TrimSpace(input.Label)
	if label == "" {
		return nil, apperrors.NewValidationError("Label", "label is required")
	}
	if input.Visibility != nil && !isPrivateVisibility(*input.Visibility) {
		return nil, privateListViewOnlyError()
	}

	developerName, err := developerNameForCreate(input)
	if err != nil {
		return nil, err
	}
	if err := s.ensureDeveloperNameAvailable(ctx, objectAPIName, developerName, ""); err != nil {
		return nil, err
	}

	ownerID := strings.TrimSpace(user.ID)
	visibility := listViewVisibilityPrivate
	fields := copyStringSlice(input.Fields)
	if fields == nil {
		fields = []string{}
	}

	view := &models.ListView{
		ID:            id,
		ObjectAPIName: objectAPIName,
		Label:         label,
		FilterExpr:    input.FilterExpr,
		FilterLogic:   input.FilterLogic,
		SortField:     input.SortField,
		SortDirection: input.SortDirection,
		Fields:        fields,
		OwnerID:       &ownerID,
		Visibility:    &visibility,
		DeveloperName: &developerName,
	}
	if err := s.repo.CreateListView(ctx, view); err != nil {
		if !apperrors.IsInternal(err) && apperrors.IsConflict(err) {
			return nil, listViewDeveloperNameConflict(developerName)
		}
		return nil, apperrors.NewInternalError("failed to create private list view", err)
	}
	return cloneListView(view), nil
}

func (s *ListViewService) GetPrivateForWrite(ctx context.Context, user *models.UserSession, id string) (*models.ListView, error) {
	if err := s.requirePrivateWrite(ctx, user); err != nil {
		return nil, err
	}
	if s == nil || s.repo == nil {
		return nil, apperrors.NewInternalError("list view repository unavailable", nil)
	}
	id = strings.TrimSpace(id)
	if id == "" {
		return nil, apperrors.NewValidationError("Id", "list view ID is required")
	}
	existing, err := s.repo.GetListViewForUpdate(ctx, id)
	if err != nil {
		return nil, listViewReadError(err, id)
	}
	if err := authorizeExistingPrivateListView(existing, user.ID); err != nil {
		return nil, err
	}
	return cloneListView(existing), nil
}

func (s *ListViewService) UpdatePrivate(ctx context.Context, user *models.UserSession, id string, patch ListViewPatch) (*models.ListView, error) {
	if err := s.requirePrivateWrite(ctx, user); err != nil {
		return nil, err
	}
	if s == nil || s.repo == nil {
		return nil, apperrors.NewInternalError("list view repository unavailable", nil)
	}
	id = strings.TrimSpace(id)
	if id == "" {
		return nil, apperrors.NewValidationError("Id", "list view ID is required")
	}

	existing, err := s.repo.GetListViewForUpdate(ctx, id)
	if err != nil {
		return nil, listViewReadError(err, id)
	}
	if err := authorizeExistingPrivateListView(existing, user.ID); err != nil {
		return nil, err
	}

	writePatch := ListViewPatch{}
	if patch.Label != nil {
		label := strings.TrimSpace(*patch.Label)
		if label == "" {
			return nil, apperrors.NewValidationError("Label", "label is required")
		}
		writePatch.Label = &label
	}
	if patch.FilterExpr != nil {
		filterExpr := *patch.FilterExpr
		writePatch.FilterExpr = &filterExpr
	}
	if patch.FilterLogic != nil {
		filterLogic := *patch.FilterLogic
		writePatch.FilterLogic = &filterLogic
	}
	if patch.SortField != nil {
		sortField := *patch.SortField
		writePatch.SortField = &sortField
	}
	if patch.SortDirection != nil {
		sortDirection := *patch.SortDirection
		writePatch.SortDirection = &sortDirection
	}
	if patch.Fields != nil {
		fields := copyStringSlice(*patch.Fields)
		writePatch.Fields = &fields
	}
	if patch.DeveloperName != nil {
		developerName, err := validateProvidedDeveloperName(*patch.DeveloperName)
		if err != nil {
			return nil, err
		}
		if err := s.ensureDeveloperNameAvailable(ctx, existing.ObjectAPIName, developerName, id); err != nil {
			return nil, err
		}
		writePatch.DeveloperName = &developerName
	}

	ownerID := strings.TrimSpace(user.ID)
	if listViewPatchIsEmpty(writePatch) {
		return cloneListView(existing), nil
	}
	if err := s.repo.UpdateListViewPatch(ctx, id, ownerID, writePatch); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, apperrors.NewNotFoundError("list view", id)
		}
		if !apperrors.IsInternal(err) && apperrors.IsConflict(err) {
			return nil, listViewDeveloperNameConflict(developerNameForPatchConflict(writePatch, existing))
		}
		return nil, apperrors.NewInternalError("failed to update private list view", err)
	}
	updated, err := s.repo.GetListView(ctx, id)
	if err != nil {
		return nil, listViewReadError(err, id)
	}
	if err := authorizeExistingPrivateListView(updated, user.ID); err != nil {
		return nil, err
	}
	return cloneListView(updated), nil
}

func (s *ListViewService) DeletePrivate(ctx context.Context, user *models.UserSession, id string) error {
	if err := s.requirePrivateWrite(ctx, user); err != nil {
		return err
	}
	if s == nil || s.repo == nil {
		return apperrors.NewInternalError("list view repository unavailable", nil)
	}
	id = strings.TrimSpace(id)
	if id == "" {
		return apperrors.NewValidationError("Id", "list view ID is required")
	}

	existing, err := s.repo.GetListViewForUpdate(ctx, id)
	if err != nil {
		return listViewReadError(err, id)
	}
	if err := authorizeExistingPrivateListView(existing, user.ID); err != nil {
		return err
	}
	ownerID := strings.TrimSpace(user.ID)
	if err := s.repo.DeletePrivateListView(ctx, id, ownerID); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return apperrors.NewNotFoundError("list view", id)
		}
		return apperrors.NewInternalError("failed to delete private list view", err)
	}
	return nil
}

func (s *ListViewService) UpsertPrivateByDeveloperName(ctx context.Context, user *models.UserSession, objectAPIName string, developerName string, input *models.ListView, patch ListViewPatch) (*models.ListView, bool, error) {
	developerName, err := validateProvidedDeveloperName(developerName)
	if err != nil {
		return nil, false, err
	}
	existing, found, err := s.FindPrivateByDeveloperNameForWrite(ctx, user, objectAPIName, developerName)
	if err != nil {
		return nil, false, err
	}
	if !found {
		if input == nil {
			input = &models.ListView{}
		} else {
			input = cloneListView(input)
		}
		input.ObjectAPIName = objectAPIName
		input.DeveloperName = &developerName
		created, createErr := s.CreatePrivate(ctx, user, input)
		return created, true, createErr
	}
	patch.DeveloperName = nil
	updated, err := s.UpdatePrivate(ctx, user, existing.ID, patch)
	return updated, false, err
}

func (s *ListViewService) FindPrivateByDeveloperNameForWrite(ctx context.Context, user *models.UserSession, objectAPIName string, developerName string) (*models.ListView, bool, error) {
	if err := s.requirePrivateWrite(ctx, user); err != nil {
		return nil, false, err
	}
	if s == nil || s.repo == nil {
		return nil, false, apperrors.NewInternalError("list view repository unavailable", nil)
	}
	objectAPIName = strings.TrimSpace(objectAPIName)
	if objectAPIName == "" {
		return nil, false, apperrors.NewValidationError("ObjectApiName", "object API name is required")
	}
	developerName, err := validateProvidedDeveloperName(developerName)
	if err != nil {
		return nil, false, err
	}
	existing, err := s.repo.GetListViewByDeveloperNameForUpdate(ctx, objectAPIName, developerName)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, false, nil
		}
		return nil, false, apperrors.NewInternalError("failed to check list view developer name", err)
	}
	if err := authorizeExistingPrivateListView(existing, user.ID); err != nil {
		return nil, false, err
	}
	return cloneListView(existing), true, nil
}

func (s *ListViewService) requirePrivateWrite(ctx context.Context, user *models.UserSession) error {
	if user == nil || strings.TrimSpace(user.ID) == "" {
		return apperrors.NewUnauthorizedError("private list-view writes require an authenticated user")
	}
	if s == nil || s.permissions == nil || !s.permissions.HasPermission(ctx, user, string(constants.PermCreateCustomizeFilters)) {
		return apperrors.NewPermissionError("write", "private list view")
	}
	return nil
}

func (s *ListViewService) ensureDeveloperNameAvailable(ctx context.Context, objectAPIName, developerName, currentID string) error {
	existing, err := s.repo.GetListViewByDeveloperName(ctx, objectAPIName, developerName)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil
		}
		return apperrors.NewInternalError("failed to check list view developer name", err)
	}
	if existing != nil && strings.TrimSpace(existing.ID) != strings.TrimSpace(currentID) {
		return apperrors.NewConflictError("list view", "DeveloperName", developerName)
	}
	return nil
}

func developerNameForCreate(input *models.ListView) (string, error) {
	if input.DeveloperName != nil && strings.TrimSpace(*input.DeveloperName) != "" {
		return validateProvidedDeveloperName(*input.DeveloperName)
	}
	generated := utils.SanitizeDeveloperName(strings.TrimSpace(input.Label))
	if generated == "" {
		return "", apperrors.NewValidationError("DeveloperName", "developer name is required")
	}
	return generated, nil
}

func validateProvidedDeveloperName(value string) (string, error) {
	developerName := strings.TrimSpace(value)
	if developerName == "" {
		return "", apperrors.NewValidationError("DeveloperName", "developer name is required")
	}
	if utils.SanitizeDeveloperName(developerName) != developerName {
		return "", apperrors.NewValidationError("DeveloperName", "developer name must contain only letters, numbers, and underscores and start with a letter")
	}
	return developerName, nil
}

func authorizeExistingPrivateListView(view *models.ListView, callerID string) error {
	if view == nil {
		return apperrors.NewNotFoundError("list view", "")
	}
	if !listViewIsPrivate(view) {
		return privateListViewOnlyError()
	}
	if view.OwnerID == nil || strings.TrimSpace(*view.OwnerID) != strings.TrimSpace(callerID) {
		return apperrors.NewNotFoundError("list view", strings.TrimSpace(view.ID))
	}
	return nil
}

func listViewIsPrivate(view *models.ListView) bool {
	return view != nil && view.Visibility != nil && isPrivateVisibility(*view.Visibility)
}

func isPrivateVisibility(visibility string) bool {
	return strings.EqualFold(strings.TrimSpace(visibility), listViewVisibilityPrivate)
}

func privateListViewOnlyError() error {
	return apperrors.NewForbiddenError("only private list views are supported")
}

func listViewPatchIsEmpty(patch ListViewPatch) bool {
	return patch.Label == nil &&
		patch.FilterExpr == nil &&
		patch.FilterLogic == nil &&
		patch.SortField == nil &&
		patch.SortDirection == nil &&
		patch.Fields == nil &&
		patch.DeveloperName == nil
}

func listViewReadError(err error, id string) error {
	if errors.Is(err, sql.ErrNoRows) {
		return apperrors.NewNotFoundError("list view", id)
	}
	return apperrors.NewInternalError("failed to read list view", err)
}

func listViewDeveloperNameConflict(developerName string) error {
	return apperrors.NewConflictError("list view", constants.FieldSysListView_DeveloperName, strings.TrimSpace(developerName))
}

func developerNameForPatchConflict(patch ListViewPatch, existing *models.ListView) string {
	if patch.DeveloperName != nil {
		return *patch.DeveloperName
	}
	if existing != nil && existing.DeveloperName != nil {
		return *existing.DeveloperName
	}
	return ""
}

func cloneListView(view *models.ListView) *models.ListView {
	if view == nil {
		return nil
	}
	out := *view
	out.Fields = copyStringSlice(view.Fields)
	if view.OwnerID != nil {
		ownerID := *view.OwnerID
		out.OwnerID = &ownerID
	}
	if view.Visibility != nil {
		visibility := *view.Visibility
		out.Visibility = &visibility
	}
	if view.DeveloperName != nil {
		developerName := *view.DeveloperName
		out.DeveloperName = &developerName
	}
	return &out
}

func copyStringSlice(values []string) []string {
	if values == nil {
		return nil
	}
	return append([]string{}, values...)
}
