package permissions

import (
	"context"
	"fmt"
	"strings"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

// PermRule defines per-object permission flags used during seeding.
type PermRule struct {
	Read, Create, Edit, Delete, ViewAll, ModifyAll bool
}

// profilePermRules returns the default permission rule matrix for known profiles.
// Unknown profiles fall back to StandardUser defaults.
var profilePermRules = map[string]map[string]PermRule{
	constants.ProfileSystemAdmin: {
		"default": {true, true, true, true, true, true},
	},
	constants.ProfileStandardUser: {
		"system":   {true, false, false, false, false, false},
		"business": {true, true, true, false, false, false},
	},
}

// resolvePermRule determines the permission rule for a given profile and object schema.
func resolvePermRule(profileID string, schema *models.ObjectMetadata) PermRule {
	rules, ok := profilePermRules[profileID]
	if !ok {
		rules = profilePermRules[constants.ProfileStandardUser]
	}
	if r, ok := rules["default"]; ok {
		return r
	}
	category := "business"
	if constants.IsSystemTable(schema.APIName) {
		category = "system"
	}
	if r, ok := rules[category]; ok {
		return r
	}
	return PermRule{}
}

// BuildPermissionsForProfileAndSchema builds ObjectPermissions and FieldPermissions
// for a single profile+schema pair. Returns nil slices when no permissions should be granted.
func BuildPermissionsForProfileAndSchema(profileID, permSetID string, schema *models.ObjectMetadata) ([]models.ObjectPermissions, []models.FieldPermissions) {
	if schema != nil && schema.UsesDerivedAccessPolicy() {
		return nil, nil
	}
	rule := resolvePermRule(profileID, schema)

	var objPerms []models.ObjectPermissions
	var fieldPerms []models.FieldPermissions

	if rule.Read || rule.Create || rule.Edit || rule.Delete || rule.ViewAll || rule.ModifyAll {
		objPerms = append(objPerms, models.ObjectPermissions{
			ParentID:                    permSetID,
			SobjectType:                 schema.APIName,
			PermissionsRead:             rule.Read,
			PermissionsCreate:           rule.Create,
			PermissionsEdit:             rule.Edit,
			PermissionsDelete:           rule.Delete,
			PermissionsViewAllRecords:   rule.ViewAll,
			PermissionsModifyAllRecords: rule.ModifyAll,
		})
	}

	if rule.Read || rule.Edit {
		for _, field := range schema.Fields {
			fieldPerms = append(fieldPerms, models.FieldPermissions{
				ParentID:        permSetID,
				SobjectType:     schema.APIName,
				Field:           field.APIName,
				PermissionsRead: rule.Read,
				PermissionsEdit: rule.Edit,
			})
		}
	}

	return objPerms, fieldPerms
}

func (ps *PermissionService) seedPermissionRows(
	ctx context.Context,
	objectPerms []models.ObjectPermissions,
	fieldPerms []models.FieldPermissions,
) error {
	if len(objectPerms) > 0 {
		if err := ps.repo.BatchUpsertObjectPermissions(ctx, objectPerms); err != nil {
			return err
		}
	}
	if len(fieldPerms) > 0 {
		if err := ps.repo.BatchUpsertFieldPermissions(ctx, fieldPerms); err != nil {
			return err
		}
	}
	return nil
}

func buildProfilePermissionSetMap(profiles []*models.Profile) []struct{ ID, Name string } {
	profileInfos := make([]struct{ ID, Name string }, 0, len(profiles))
	for _, profile := range profiles {
		if profile == nil {
			continue
		}
		profileInfos = append(profileInfos, struct{ ID, Name string }{
			ID:   profile.ID,
			Name: profile.Name,
		})
	}
	return profileInfos
}

// SeedPermissionsForProfile seeds ObjectPermissions and FieldPermissions for a single
// profile across all provided schemas.
// Caller is responsible for invalidating the permission cache after commit.
func (ps *PermissionService) SeedPermissionsForProfile(ctx context.Context, profileID, permSetID string, schemas []*models.ObjectMetadata) error {
	var allObjPerms []models.ObjectPermissions
	var allFieldPerms []models.FieldPermissions

	for _, schema := range schemas {
		objPerms, fieldPerms := BuildPermissionsForProfileAndSchema(profileID, permSetID, schema)
		allObjPerms = append(allObjPerms, objPerms...)
		allFieldPerms = append(allFieldPerms, fieldPerms...)
	}

	if err := ps.seedPermissionRows(ctx, allObjPerms, allFieldPerms); err != nil {
		return fmt.Errorf("seed permissions for profile %s: %w", profileID, err)
	}

	log.Info().Str("profileID", profileID).
		Int("objectPerms", len(allObjPerms)).Int("fieldPerms", len(allFieldPerms)).
		Msg("Seeded permissions for profile")
	return nil
}

// SeedPermissionsForObject seeds ObjectPermissions and FieldPermissions for a single
// object across all profiles.
// Caller is responsible for invalidating the permission cache after commit.
func (ps *PermissionService) SeedPermissionsForObject(ctx context.Context, objectAPIName string) error {
	schema := ps.metadata.GetSchema(ctx, objectAPIName)
	if schema == nil {
		return fmt.Errorf("schema not found for object %s", objectAPIName)
	}

	profiles, err := ps.GetAllProfiles(ctx)
	if err != nil {
		return fmt.Errorf("get all profiles: %w", err)
	}
	permSetIDs, err := ps.BulkEnsureProfilePermissionSets(ctx, buildProfilePermissionSetMap(profiles))
	if err != nil {
		return fmt.Errorf("ensure permission sets for object %s: %w", objectAPIName, err)
	}

	var allObjPerms []models.ObjectPermissions
	var allFieldPerms []models.FieldPermissions

	for _, profile := range profiles {
		permSetID, ok := permSetIDs[profile.ID]
		if !ok {
			return fmt.Errorf("missing permission set for profile %s while seeding object %s", profile.ID, objectAPIName)
		}

		objPerms, fieldPerms := BuildPermissionsForProfileAndSchema(profile.ID, permSetID, schema)
		allObjPerms = append(allObjPerms, objPerms...)
		allFieldPerms = append(allFieldPerms, fieldPerms...)
	}

	if err := ps.seedPermissionRows(ctx, allObjPerms, allFieldPerms); err != nil {
		return fmt.Errorf("seed permissions for object %s: %w", objectAPIName, err)
	}

	log.Info().Str("object", objectAPIName).
		Int("profiles", len(profiles)).Int("objectPerms", len(allObjPerms)).Int("fieldPerms", len(allFieldPerms)).
		Msg("Seeded permissions for object")
	return nil
}

// SeedFieldPermissionsForField seeds FieldPermissions for a single field across all
// profiles.
// Caller is responsible for invalidating the permission cache after commit.
func (ps *PermissionService) SeedFieldPermissionsForField(ctx context.Context, objectAPIName, fieldAPIName string) error {
	return ps.SeedFieldPermissionsForFields(ctx, objectAPIName, []string{fieldAPIName})
}

// SeedFieldPermissionsForFields seeds FieldPermissions for specific fields across all
// profiles.
// Caller is responsible for invalidating the permission cache after commit.
func (ps *PermissionService) SeedFieldPermissionsForFields(ctx context.Context, objectAPIName string, fieldAPINames []string) error {
	if len(fieldAPINames) == 0 {
		return nil
	}

	profiles, err := ps.GetAllProfiles(ctx)
	if err != nil {
		return fmt.Errorf("get all profiles: %w", err)
	}

	schema := ps.metadata.GetSchema(ctx, objectAPIName)
	if schema == nil {
		return fmt.Errorf("schema not found for object %s", objectAPIName)
	}
	if schema.UsesDerivedAccessPolicy() {
		return nil
	}
	permSetIDs, err := ps.BulkEnsureProfilePermissionSets(ctx, buildProfilePermissionSetMap(profiles))
	if err != nil {
		return fmt.Errorf("ensure permission sets for fields on %s: %w", objectAPIName, err)
	}

	fieldNamesByKey := make(map[string]string, len(schema.Fields))
	for _, field := range schema.Fields {
		fieldNamesByKey[strings.ToLower(field.APIName)] = field.APIName
	}
	normalizedFieldNames := make([]string, 0, len(fieldAPINames))
	seenFieldNames := make(map[string]bool, len(fieldAPINames))
	for _, fieldAPIName := range fieldAPINames {
		key := strings.ToLower(fieldAPIName)
		actualFieldName, ok := fieldNamesByKey[key]
		if !ok || seenFieldNames[key] {
			continue
		}
		seenFieldNames[key] = true
		normalizedFieldNames = append(normalizedFieldNames, actualFieldName)
	}
	if len(normalizedFieldNames) == 0 {
		return nil
	}

	var allFieldPerms []models.FieldPermissions

	for _, profile := range profiles {
		permSetID, ok := permSetIDs[profile.ID]
		if !ok {
			return fmt.Errorf("missing permission set for profile %s while seeding fields on %s", profile.ID, objectAPIName)
		}

		rule := resolvePermRule(profile.ID, schema)
		if rule.Read || rule.Edit {
			for _, fieldAPIName := range normalizedFieldNames {
				allFieldPerms = append(allFieldPerms, models.FieldPermissions{
					ParentID:        permSetID,
					SobjectType:     objectAPIName,
					Field:           fieldAPIName,
					PermissionsRead: rule.Read,
					PermissionsEdit: rule.Edit,
				})
			}
		}
	}

	if err := ps.seedPermissionRows(ctx, nil, allFieldPerms); err != nil {
		return fmt.Errorf("seed field permissions for %s: %w", objectAPIName, err)
	}

	log.Info().Str("object", objectAPIName).
		Int("fieldPerms", len(allFieldPerms)).
		Msg("Seeded field permissions for new fields")
	return nil
}
