package automation

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/nexuscrm/backend/pkg/utils"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// RecipientEntry represents a single recipient configuration in a WorkflowAlert.
// Matches the JSON format stored in _System_WorkflowAlert.Recipients.
type RecipientEntry struct {
	Type      string `json:"type"`
	Recipient string `json:"recipient,omitempty"`
	Field     string `json:"field,omitempty"`
}

// resolveAlertRecipients parses a Recipients JSON array from a WorkflowAlert
// and resolves each entry to one or more email addresses.
func (s *ApprovalService) resolveAlertRecipients(ctx context.Context, recipientsJSON string, targetRecord models.SObject, user *models.UserSession) []string {
	if strings.TrimSpace(recipientsJSON) == "" || recipientsJSON == "[]" {
		return nil
	}

	var entries []RecipientEntry
	if err := json.Unmarshal([]byte(recipientsJSON), &entries); err != nil {
		s.logger.Error(fmt.Sprintf("Failed to parse Recipients JSON: %s", recipientsJSON), err)
		return nil
	}

	var emails []string
	for _, entry := range entries {
		resolved := s.resolveRecipientEntry(ctx, entry, targetRecord, user)
		emails = append(emails, resolved...)
	}

	return deduplicateEmails(emails)
}

// resolveRecipientEntry resolves a single RecipientEntry to email addresses.
func (s *ApprovalService) resolveRecipientEntry(ctx context.Context, entry RecipientEntry, targetRecord models.SObject, user *models.UserSession) []string {
	switch strings.ToLower(entry.Type) {
	case "user":
		return s.resolveUserRecipient(ctx, entry.Recipient)
	case "owner":
		return s.resolveOwnerRecipient(ctx, targetRecord)
	case "role":
		return s.resolveRoleRecipient(ctx, entry.Recipient, user)
	case "group":
		return s.resolveGroupRecipient(ctx, entry.Recipient, user)
	case "email":
		if email := strings.TrimSpace(entry.Recipient); email != "" {
			return []string{email}
		}
		if field := strings.TrimSpace(entry.Field); field != "" {
			if email, ok := targetRecord[field].(string); ok {
				if email = strings.TrimSpace(email); email != "" {
					return []string{email}
				}
			}
		}
		return nil
	case "accountowner":
		return s.resolveAccountOwnerRecipient(ctx, targetRecord)
	case "userlookup":
		return s.resolveUserLookupRecipient(ctx, entry.Field, targetRecord)
	case "contactlookup":
		return s.resolveContactLookupRecipient(ctx, entry.Field, targetRecord)
	case "campaignmemberderivedowner":
		return s.resolveCampaignMemberDerivedOwner(ctx, targetRecord)
	default:
		s.logger.Warn(fmt.Sprintf("Unsupported recipient type: %s", entry.Type))
		return nil
	}
}

// resolveUserLookupRecipient reads a User-reference field on the target record
// and resolves the referenced User's email.
func (s *ApprovalService) resolveUserLookupRecipient(ctx context.Context, field string, targetRecord models.SObject) []string {
	field = strings.TrimSpace(field)
	if field == "" {
		return nil
	}
	userID, _ := targetRecord[field].(string)
	if strings.TrimSpace(userID) == "" {
		return nil
	}
	return s.resolveUserRecipient(ctx, userID)
}

// resolveContactLookupRecipient reads a Contact-reference field on the target
// record, fetches the referenced Contact, and returns its Email.
func (s *ApprovalService) resolveContactLookupRecipient(ctx context.Context, field string, targetRecord models.SObject) []string {
	field = strings.TrimSpace(field)
	if field == "" {
		return nil
	}
	contactID, _ := targetRecord[field].(string)
	if strings.TrimSpace(contactID) == "" {
		return nil
	}
	contact, err := s.persistence.GetRaw(ctx, "Contact", contactID)
	if err != nil || contact == nil {
		return nil
	}
	email := utils.GetConfigString(contact, constants.FieldEmail)
	if email == "" {
		return nil
	}
	return []string{email}
}

// resolveCampaignMemberDerivedOwner resolves the owner of a CampaignMember target record.
func (s *ApprovalService) resolveCampaignMemberDerivedOwner(ctx context.Context, targetRecord models.SObject) []string {
	ownerID, _ := targetRecord[constants.FieldOwnerID].(string)
	if strings.TrimSpace(ownerID) == "" {
		s.logger.Warn("campaignMemberDerivedOwner: target record has no OwnerId")
		return nil
	}
	return s.resolveUserRecipient(ctx, ownerID)
}

// resolveUserRecipient resolves a user recipient (email or User ID) to an email.
func (s *ApprovalService) resolveUserRecipient(ctx context.Context, recipient string) []string {
	if s.actions == nil {
		return nil
	}
	email, err := s.actions.ResolveRecipient(ctx, recipient)
	if err != nil {
		s.logger.Warn(fmt.Sprintf("Failed to resolve user recipient '%s': %v", recipient, err))
		return nil
	}
	return []string{email}
}

// resolveOwnerRecipient resolves the record owner to an email.
func (s *ApprovalService) resolveOwnerRecipient(ctx context.Context, targetRecord models.SObject) []string {
	ownerID, _ := targetRecord[constants.FieldOwnerID].(string)
	if ownerID == "" {
		return nil
	}
	return s.resolveUserRecipient(ctx, ownerID)
}

// resolveRoleRecipient finds all active users with a given role name and returns their emails.
func (s *ApprovalService) resolveRoleRecipient(ctx context.Context, roleName string, user *models.UserSession) []string {
	if strings.TrimSpace(roleName) == "" {
		return nil
	}

	// Find the role by name
	roleFilter := fmt.Sprintf("Name = '%s'", strings.ReplaceAll(roleName, "'", "''"))
	roleResult, err := s.query.QueryWithFilter(ctx, constants.TableUserRole, roleFilter, user, "", "", 1)
	if err != nil || roleResult == nil || len(roleResult.Records) == 0 {
		s.logger.Warn(fmt.Sprintf("Role '%s' not found", roleName))
		return nil
	}

	roleID := utils.GetConfigString(roleResult.Records[0], constants.FieldID)
	if roleID == "" {
		return nil
	}

	// Find all active users with this role
	userFilter := fmt.Sprintf("UserRoleId = '%s'", strings.ReplaceAll(roleID, "'", "''"))
	userResult, err := s.query.QueryWithFilter(ctx, constants.TableUser, userFilter, user, "", "", 200)
	if err != nil || userResult == nil {
		return nil
	}

	var emails []string
	for _, u := range userResult.Records {
		email := utils.GetConfigString(u, constants.FieldEmail)
		if email != "" {
			emails = append(emails, email)
		}
	}
	return emails
}

// resolveGroupRecipient finds all members of a public group and returns their emails.
func (s *ApprovalService) resolveGroupRecipient(ctx context.Context, groupName string, user *models.UserSession) []string {
	if strings.TrimSpace(groupName) == "" {
		return nil
	}

	// Find the group by name
	groupFilter := fmt.Sprintf("Name = '%s'", strings.ReplaceAll(groupName, "'", "''"))
	groupResult, err := s.query.QueryWithFilter(ctx, constants.TableGroup, groupFilter, user, "", "", 1)
	if err != nil || groupResult == nil || len(groupResult.Records) == 0 {
		s.logger.Warn(fmt.Sprintf("Group '%s' not found", groupName))
		return nil
	}

	groupID := utils.GetConfigString(groupResult.Records[0], constants.FieldID)
	if groupID == "" {
		return nil
	}

	// Find all members of this group
	memberFilter := fmt.Sprintf("GroupId = '%s'", strings.ReplaceAll(groupID, "'", "''"))
	memberResult, err := s.query.QueryWithFilter(ctx, "GroupMember", memberFilter, user, "", "", 200)
	if err != nil || memberResult == nil {
		return nil
	}

	var emails []string
	for _, member := range memberResult.Records {
		userOrGroupID := utils.GetConfigString(member, "UserOrGroupId")
		if userOrGroupID != "" {
			resolved := s.resolveUserRecipient(ctx, userOrGroupID)
			emails = append(emails, resolved...)
		}
	}
	return emails
}

// resolveAccountOwnerRecipient resolves the account owner of the record.
func (s *ApprovalService) resolveAccountOwnerRecipient(ctx context.Context, targetRecord models.SObject) []string {
	accountID, _ := targetRecord["AccountId"].(string)
	if accountID == "" {
		return nil
	}

	account, err := s.persistence.GetRaw(ctx, "Account", accountID)
	if err != nil || account == nil {
		return nil
	}

	ownerID, _ := account[constants.FieldOwnerID].(string)
	if ownerID == "" {
		return nil
	}
	return s.resolveUserRecipient(ctx, ownerID)
}

// deduplicateEmails returns unique, non-empty, lowercased email addresses.
func deduplicateEmails(emails []string) []string {
	seen := make(map[string]struct{})
	var result []string
	for _, email := range emails {
		email = strings.TrimSpace(email)
		if email == "" {
			continue
		}
		lower := strings.ToLower(email)
		if _, exists := seen[lower]; exists {
			continue
		}
		seen[lower] = struct{}{}
		result = append(result, email)
	}
	return result
}
