package notifications

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/smtp"
	"strings"
	"time"

	"bookra/apps/backend/internal/config"
	"bookra/apps/backend/internal/db"
	"bookra/apps/backend/internal/domain"
)

var ErrUnsupportedChannel = errors.New("unsupported notification channel")

type DeliveryReceipt struct {
	Provider   string
	ExternalID string
}

type EmailMessage struct {
	From    string
	To      string
	Subject string
	Text    string
	HTML    string
}

type EmailProvider interface {
	Send(context.Context, EmailMessage) (DeliveryReceipt, error)
}

type Service struct {
	cfg           config.Config
	repo          db.Repository
	emailProvider EmailProvider
	now           func() time.Time
}

func NewService(cfg config.Config, repo db.Repository) *Service {
	emailProvider := EmailProvider(noopEmailProvider{})
	if cfg.SMTPHost != "" {
		emailProvider = smtpEmailProvider{cfg: cfg}
	}

	return &Service{
		cfg:           cfg,
		repo:          repo,
		emailProvider: emailProvider,
		now:           func() time.Time { return time.Now().UTC() },
	}
}

func (s *Service) DispatchDue(ctx context.Context, limit int) (domain.DispatchReminderJobsResponse, error) {
	if limit <= 0 {
		limit = 25
	}

	jobs, err := s.repo.ListDueReminderJobs(ctx, s.now(), limit)
	if err != nil {
		return domain.DispatchReminderJobsResponse{}, err
	}

	response := domain.DispatchReminderJobsResponse{}
	for _, job := range jobs {
		response.ProcessedCount++

		status := "sent"
		provider := "unknown"
		externalID := ""
		errorMessage := ""

		switch job.Channel {
		case "email":
			receipt, sendErr := s.emailProvider.Send(ctx, renderEmailMessage(s.cfg.EmailFrom, job))
			if sendErr != nil {
				status = "failed"
				errorMessage = sendErr.Error()
			} else {
				provider = receipt.Provider
				externalID = receipt.ExternalID
			}
		default:
			status = "failed"
			errorMessage = ErrUnsupportedChannel.Error()
		}

		if provider == "unknown" {
			if job.Channel == "email" {
				provider = "noop-email"
			}
		}

		if err := s.repo.MarkReminderJobDispatched(ctx, job.ID, status, s.now()); err != nil {
			return domain.DispatchReminderJobsResponse{}, err
		}
		if err := s.repo.CreateNotificationDeliveryLog(ctx, db.NotificationDeliveryLogParams{
			TenantID:      job.TenantID,
			ReminderJobID: job.ID,
			Channel:       job.Channel,
			Provider:      provider,
			Recipient:     reminderRecipient(job),
			Status:        status,
			ExternalID:    externalID,
			ErrorMessage:  errorMessage,
		}); err != nil {
			return domain.DispatchReminderJobsResponse{}, err
		}

		if status == "sent" {
			response.SentCount++
		} else {
			response.FailedCount++
		}
	}

	return response, nil
}

// SendBookingConfirmation sends a booking confirmation email to the customer
func (s *Service) SendBookingConfirmation(ctx context.Context, data BookingEmailData) error {
	if data.BusinessEmail == "" {
		data.BusinessEmail = s.cfg.EmailFrom
	}
	data.Type = EmailTypeConfirmation
	msg := RenderEmailMessage(data)
	_, err := s.emailProvider.Send(ctx, msg)
	return err
}

// SendBookingReschedule sends a reschedule notification email to the customer
func (s *Service) SendBookingReschedule(ctx context.Context, data BookingEmailData) error {
	if data.BusinessEmail == "" {
		data.BusinessEmail = s.cfg.EmailFrom
	}
	data.Type = EmailTypeReschedule
	msg := RenderEmailMessage(data)
	_, err := s.emailProvider.Send(ctx, msg)
	return err
}

// SendBookingCancellation sends a cancellation notification email to the customer
func (s *Service) SendBookingCancellation(ctx context.Context, data BookingEmailData) error {
	if data.BusinessEmail == "" {
		data.BusinessEmail = s.cfg.EmailFrom
	}
	data.Type = EmailTypeCancellation
	msg := RenderEmailMessage(data)
	_, err := s.emailProvider.Send(ctx, msg)
	return err
}

// SendBusinessNotification sends a notification to the business about a new booking
func (s *Service) SendBusinessNotification(ctx context.Context, businessEmail string, data BookingEmailData) error {
	if businessEmail == "" {
		return nil // Skip if no business email configured
	}
	data.Type = EmailTypeBusinessNotify
	msg := RenderEmailMessage(data)
	msg.To = businessEmail
	_, err := s.emailProvider.Send(ctx, msg)
	return err
}

func renderEmailMessage(from string, job db.ReminderJobRecord) EmailMessage {
	return RenderReminderEmail(from, job)
}

func renderReminderCopy(job db.ReminderJobRecord) (string, string) {
	startLabel := localizedStartsAt(job)

	if job.Locale == "cs" {
		return "Pripominka rezervace Bookra", fmt.Sprintf(
			"Dobry den %s,\n\npripominame rezervaci %s u %s na %s.\n\nReference: %s\n",
			job.CustomerName,
			job.Reference,
			job.TenantName,
			startLabel,
			job.Reference,
		)
	}

	return "Bookra booking reminder", fmt.Sprintf(
		"Hello %s,\n\nthis is a reminder for booking %s with %s at %s.\n\nReference: %s\n",
		job.CustomerName,
		job.Reference,
		job.TenantName,
		startLabel,
		job.Reference,
	)
}

func localizedStartsAt(job db.ReminderJobRecord) string {
	location, err := time.LoadLocation(job.Timezone)
	if err != nil {
		location = time.UTC
	}
	localStartsAt := job.StartsAt.In(location)
	if job.Locale == "cs" {
		return localStartsAt.Format("02.01.2006 15:04")
	}
	return localStartsAt.Format("Jan 02, 2006 15:04")
}

func reminderRecipient(job db.ReminderJobRecord) string {
	return job.CustomerEmail
}

type noopEmailProvider struct{}

func (noopEmailProvider) Send(_ context.Context, message EmailMessage) (DeliveryReceipt, error) {
	if message.To == "" {
		return DeliveryReceipt{Provider: "noop-email"}, errors.New("missing email recipient")
	}
	return DeliveryReceipt{
		Provider:   "noop-email",
		ExternalID: fmt.Sprintf("noop-email-%d", time.Now().UnixNano()),
	}, nil
}

type smtpEmailProvider struct {
	cfg config.Config
}

func (p smtpEmailProvider) Send(_ context.Context, message EmailMessage) (DeliveryReceipt, error) {
	if strings.TrimSpace(message.To) == "" {
		return DeliveryReceipt{Provider: "smtp"}, errors.New("missing email recipient")
	}

	host := strings.TrimSpace(p.cfg.SMTPHost)
	if host == "" {
		return DeliveryReceipt{Provider: "smtp"}, errors.New("smtp host is not configured")
	}

	address := net.JoinHostPort(host, strings.TrimSpace(p.cfg.SMTPPort))
	var auth smtp.Auth
	if p.cfg.SMTPUsername != "" {
		auth = smtp.PlainAuth("", p.cfg.SMTPUsername, p.cfg.SMTPPassword, host)
	}

	// Build multipart email with both plain text and HTML
	var payload string
	if message.HTML != "" {
		boundary := "BOOKRA-BOUNDARY"
		payload = strings.Join([]string{
			fmt.Sprintf("From: %s", message.From),
			fmt.Sprintf("To: %s", message.To),
			fmt.Sprintf("Subject: %s", message.Subject),
			"MIME-Version: 1.0",
			fmt.Sprintf("Content-Type: multipart/alternative; boundary=\"%s\"", boundary),
			"",
			fmt.Sprintf("--%s", boundary),
			"Content-Type: text/plain; charset=UTF-8",
			"",
			message.Text,
			"",
			fmt.Sprintf("--%s", boundary),
			"Content-Type: text/html; charset=UTF-8",
			"",
			message.HTML,
			"",
			fmt.Sprintf("--%s--", boundary),
		}, "\r\n")
	} else {
		payload = strings.Join([]string{
			fmt.Sprintf("From: %s", message.From),
			fmt.Sprintf("To: %s", message.To),
			fmt.Sprintf("Subject: %s", message.Subject),
			"MIME-Version: 1.0",
			"Content-Type: text/plain; charset=UTF-8",
			"",
			message.Text,
		}, "\r\n")
	}

	if err := smtp.SendMail(address, auth, message.From, []string{message.To}, []byte(payload)); err != nil {
		return DeliveryReceipt{Provider: "smtp"}, err
	}

	return DeliveryReceipt{
		Provider:   "smtp",
		ExternalID: fmt.Sprintf("smtp-%d", time.Now().UnixNano()),
	}, nil
}

// SendContactEmail sends a contact form submission to the business email
func (s *Service) SendContactEmail(ctx context.Context, name, email, message string) error {
	subject := fmt.Sprintf("Bookra Contact: Message from %s", name)
	text := fmt.Sprintf("Name: %s\nEmail: %s\n\nMessage:\n%s", name, email, message)
	html := fmt.Sprintf(
		"<h2>New contact form submission</h2><p><strong>Name:</strong> %s</p><p><strong>Email:</strong> %s</p><p><strong>Message:</strong></p><p>%s</p>",
		name, email, message,
	)
	msg := EmailMessage{
		From:    s.cfg.EmailFrom,
		To:      s.cfg.EmailFrom,
		Subject: subject,
		Text:    text,
		HTML:    html,
	}
	_, err := s.emailProvider.Send(ctx, msg)
	return err
}

func (s *Service) SendUsageWarning(ctx context.Context, tenantID string, locationCount, locationLimit, usagePercent int) error {
	tenant, err := s.repo.GetTenantByID(ctx, tenantID)
	if err != nil {
		return fmt.Errorf("failed to get tenant: %w", err)
	}

	// Use a placeholder admin email - in production, would get from tenant owner
	adminEmail := "admin@" + tenant.Slug + ".bookra.eu"

	emailData := UsageNotificationData{
		Type:          EmailTypeUsageWarning,
		TenantName:    tenant.Name,
		TenantSlug:    tenant.Slug,
		BusinessEmail: s.cfg.EmailFrom,
		AdminEmail:    adminEmail,
		Locale:        tenant.Locale,
		PlanCode:      tenant.PlanCode,
		LocationCount: locationCount,
		LocationLimit: locationLimit,
		UsagePercent:  usagePercent,
		UpgradeURL:    "https://bookra.eu/pricing",
		DashboardURL:  "https://bookra.eu/dashboard",
	}

	msg := RenderUsageNotificationEmail(emailData)
	_, err = s.emailProvider.Send(ctx, msg)
	return err
}

// SendRawEmail sends a pre-built email message
func (s *Service) SendRawEmail(ctx context.Context, msg EmailMessage) (DeliveryReceipt, error) {
	if msg.From == "" {
		msg.From = s.cfg.EmailFrom
	}
	return s.emailProvider.Send(ctx, msg)
}

func (s *Service) SendTrialEndingEmail(ctx context.Context, tenantID string, daysRemaining int) error {
	tenant, err := s.repo.GetTenantByID(ctx, tenantID)
	if err != nil {
		return fmt.Errorf("failed to get tenant: %w", err)
	}

	// Use a placeholder admin email - in production, would get from tenant owner
	adminEmail := "admin@" + tenant.Slug + ".bookra.eu"

	emailData := UsageNotificationData{
		Type:          EmailTypeTrialEnding,
		TenantName:    tenant.Name,
		TenantSlug:    tenant.Slug,
		BusinessEmail: s.cfg.EmailFrom,
		AdminEmail:    adminEmail,
		Locale:        tenant.Locale,
		PlanCode:      tenant.PlanCode,
		UpgradeURL:    "https://bookra.eu/pricing",
		DashboardURL:  "https://bookra.eu/dashboard",
	}

	msg := RenderUsageNotificationEmail(emailData)
	_, err = s.emailProvider.Send(ctx, msg)
	return err
}
