// Package inviteemail provides services for sending invitation emails
// to non-registered users when someone wants to share a collection with them.
package inviteemail

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/gocql/gocql"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
	dom_inviteemail "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/inviteemail"
	dom_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/user"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/repo/inviteemailratelimit"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/emailer/mailgun"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
)

// SendInviteEmailRequestDTO represents the request to send an invitation email
type SendInviteEmailRequestDTO struct {
	Email string `json:"email"`
}

// SendInviteEmailResponseDTO represents the response after sending an invitation email
type SendInviteEmailResponseDTO struct {
	Success        bool   `json:"success"`
	RemainingToday int    `json:"remaining_invites_today"`
	Message        string `json:"message"`
}

// SendInviteEmailService defines the interface for sending invitation emails
type SendInviteEmailService interface {
	Execute(ctx context.Context, inviterID gocql.UUID, req *SendInviteEmailRequestDTO) (*SendInviteEmailResponseDTO, error)
}

type sendInviteEmailServiceImpl struct {
	config          *config.Config
	logger          *zap.Logger
	userRepo        dom_user.Repository
	rateLimitRepo   inviteemailratelimit.Repository
	emailer         mailgun.Emailer
	maxEmailsPerDay int
}

// NewSendInviteEmailService creates a new instance of the send invite email service
func NewSendInviteEmailService(
	cfg *config.Config,
	logger *zap.Logger,
	userRepo dom_user.Repository,
	rateLimitRepo inviteemailratelimit.Repository,
	emailer mailgun.Emailer,
) SendInviteEmailService {
	logger = logger.Named("SendInviteEmailService")

	// Get max emails per day from config, fallback to default
	maxEmails := cfg.InviteEmail.MaxEmailsPerDay
	if maxEmails <= 0 {
		maxEmails = dom_inviteemail.DefaultMaxInviteEmailsPerDay
	}

	return &sendInviteEmailServiceImpl{
		config:          cfg,
		logger:          logger,
		userRepo:        userRepo,
		rateLimitRepo:   rateLimitRepo,
		emailer:         emailer,
		maxEmailsPerDay: maxEmails,
	}
}

func (svc *sendInviteEmailServiceImpl) Execute(ctx context.Context, inviterID gocql.UUID, req *SendInviteEmailRequestDTO) (*SendInviteEmailResponseDTO, error) {
	//
	// STEP 1: Sanitize input
	//

	req.Email = strings.ToLower(strings.TrimSpace(req.Email))

	svc.logger.Debug("Processing invite email request",
		zap.String("inviter_id", inviterID.String()),
		zap.String("invited_email", validation.MaskEmail(req.Email)))

	//
	// STEP 2: Validate input
	//

	e := make(map[string]string)
	if req.Email == "" {
		e["email"] = "Email is required"
	} else if !validation.IsValidEmail(req.Email) {
		e["email"] = "Invalid email format"
	} else if len(req.Email) > 255 {
		e["email"] = "Email is too long"
	}

	if len(e) != 0 {
		svc.logger.Warn("Validation failed", zap.Any("errors", e))
		return nil, httperror.NewForBadRequest(&e)
	}

	//
	// STEP 3: Get inviter info
	//

	inviter, err := svc.userRepo.GetByID(ctx, inviterID)
	if err != nil {
		svc.logger.Error("Failed to get inviter info",
			zap.String("inviter_id", inviterID.String()),
			zap.Error(err))
		return nil, httperror.NewForInternalServerError("Failed to process request")
	}
	if inviter == nil {
		svc.logger.Error("Inviter not found",
			zap.String("inviter_id", inviterID.String()))
		return nil, httperror.NewForUnauthorizedWithSingleField("user", "User not found")
	}

	//
	// STEP 4: Check rate limit
	//

	today := time.Now().UTC().Truncate(24 * time.Hour)
	dailyCount, err := svc.rateLimitRepo.GetDailyEmailCount(ctx, inviterID, today)
	if err != nil {
		svc.logger.Warn("Failed to get rate limit count, proceeding with caution",
			zap.String("inviter_id", inviterID.String()),
			zap.Error(err))
		// Fail open but log - don't block users due to rate limit DB issues
		dailyCount = 0
	}

	if dailyCount >= svc.maxEmailsPerDay {
		svc.logger.Warn("Rate limit exceeded",
			zap.String("inviter_id", inviterID.String()),
			zap.Int("daily_count", dailyCount),
			zap.Int("max_per_day", svc.maxEmailsPerDay))
		return &SendInviteEmailResponseDTO{
			Success:        false,
			RemainingToday: 0,
			Message:        "Daily invitation limit reached. You can send more invitations tomorrow.",
		}, nil
	}

	//
	// STEP 5: Check if recipient already has an account
	//

	exists, err := svc.userRepo.CheckIfExistsByEmail(ctx, req.Email)
	if err != nil {
		svc.logger.Error("Failed to check if user exists",
			zap.String("email", validation.MaskEmail(req.Email)),
			zap.Error(err))
		return nil, httperror.NewForInternalServerError("Failed to process request")
	}
	if exists {
		svc.logger.Debug("User already has account",
			zap.String("email", validation.MaskEmail(req.Email)))
		return &SendInviteEmailResponseDTO{
			Success:        false,
			RemainingToday: svc.maxEmailsPerDay - dailyCount,
			Message:        "This user already has an account. You can share with them directly.",
		}, nil
	}

	//
	// STEP 6: Send invitation email
	//

	if err := svc.sendInvitationEmail(ctx, inviter.Email, req.Email); err != nil {
		svc.logger.Error("Failed to send invitation email",
			zap.String("invited_email", validation.MaskEmail(req.Email)),
			zap.Error(err))
		return nil, httperror.NewForInternalServerError("Failed to send invitation email. Please try again.")
	}

	//
	// STEP 7: Increment rate limit counter
	//

	if err := svc.rateLimitRepo.IncrementDailyEmailCount(ctx, inviterID, today); err != nil {
		svc.logger.Warn("Failed to increment rate limit counter",
			zap.String("inviter_id", inviterID.String()),
			zap.Error(err))
		// Don't fail the request, email was already sent
	}

	remaining := svc.maxEmailsPerDay - dailyCount - 1

	svc.logger.Info("Invitation email sent successfully",
		zap.String("inviter_id", inviterID.String()),
		zap.String("invited_email", validation.MaskEmail(req.Email)),
		zap.Int("remaining_today", remaining))

	return &SendInviteEmailResponseDTO{
		Success:        true,
		RemainingToday: remaining,
		Message:        fmt.Sprintf("Invitation sent to %s", req.Email),
	}, nil
}

func (svc *sendInviteEmailServiceImpl) sendInvitationEmail(ctx context.Context, inviterEmail, recipientEmail string) error {
	frontendURL := svc.emailer.GetFrontendDomainName()
	registerLink := fmt.Sprintf("%s/register", frontendURL)

	subject := fmt.Sprintf("%s wants to share files with you on MapleFile", inviterEmail)

	htmlContent := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
    <h2 style="color: #1e40af;">You've been invited to MapleFile!</h2>
    <p><strong>%s</strong> wants to share encrypted files with you.</p>
    <p>MapleFile is a secure, end-to-end encrypted file storage service. To receive the shared files, you'll need to create a free account.</p>
    <p style="margin: 30px 0;">
        <a href="%s" style="background-color: #1e40af; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block; font-weight: 500;">
            Create Your Account
        </a>
    </p>
    <p>Once you've registered, let <strong>%s</strong> know and they can share their files with you.</p>
    <hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
    <p style="color: #666; font-size: 14px;">If you didn't expect this invitation, you can safely ignore this email.</p>
</body>
</html>
`, inviterEmail, registerLink, inviterEmail)

	return svc.emailer.Send(ctx, svc.emailer.GetSenderEmail(), subject, recipientEmail, htmlContent)
}
