package service

import (
	"context"
	"errors"
	"fmt"
	"html"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	filemanager "wucher/internal/domain/file_manager"
	mastersettings "wucher/internal/domain/master_settings"
	"wucher/internal/queue"
)

const (
	defaultEmailBrandingCompanyName = "Wucher"
	authEmailLogoCID                = "branding-email-logo"
	authEmailLogoFilename           = "email-logo.png"
	authEmailLogoFetchTimeout       = 10 * time.Second
	authEmailLogoMaxBytes           = 5 << 20
	authEmailLogoPresignTTL         = time.Minute
)

type AuthEmailBranding struct {
	CompanyName    string
	LogoContentID  string
	LogoAttachment *queue.EmailAttachment
}

type BuiltAuthEmail struct {
	Subject     string
	TextBody    string
	HTMLBody    string
	Attachments []queue.EmailAttachment
}

type AuthEmailBrandingResolver interface {
	ResolveEmailBranding(ctx context.Context) AuthEmailBranding
}

type authEmailBrandingResolver struct {
	masterSvc  mastersettings.Service
	fileSvc    filemanager.Service
	storage    filemanager.ObjectStorage
	httpClient *http.Client
}

func NewAuthEmailBrandingResolver(masterSvc mastersettings.Service, fileSvc filemanager.Service, storage filemanager.ObjectStorage) AuthEmailBrandingResolver {
	return &authEmailBrandingResolver{
		masterSvc: masterSvc,
		fileSvc:   fileSvc,
		storage:   storage,
		httpClient: &http.Client{
			Timeout: authEmailLogoFetchTimeout,
		},
	}
}

func (r *authEmailBrandingResolver) ResolveEmailBranding(ctx context.Context) AuthEmailBranding {
	branding := AuthEmailBranding{
		CompanyName: defaultEmailBrandingCompanyName,
	}
	if r == nil || r.masterSvc == nil {
		return branding
	}

	rows, _, err := r.masterSvc.List(ctx, "", "-updated_at", 1, 0)
	if err != nil || len(rows) == 0 {
		return branding
	}
	row := rows[0]
	if companyName := strings.TrimSpace(row.CompanyName); companyName != "" {
		branding.CompanyName = companyName
	}
	if len(row.LogoFileID) == 0 {
		return branding
	}

	attachment, err := r.resolveInlineLogoAttachment(ctx, row.LogoFileID)
	if err != nil || attachment == nil {
		return branding
	}
	branding.LogoContentID = attachment.ContentID
	branding.LogoAttachment = attachment
	return branding
}

func (r *authEmailBrandingResolver) resolveInlineLogoAttachment(ctx context.Context, fileID []byte) (*queue.EmailAttachment, error) {
	if r == nil || r.fileSvc == nil {
		return nil, errors.New("file manager service is not configured")
	}
	if r.storage == nil {
		return nil, errors.New("object storage is not configured")
	}

	fileRow, err := r.fileSvc.GetFileByID(ctx, fileID)
	if err != nil {
		return nil, fmt.Errorf("get managed branding file: %w", err)
	}
	if fileRow == nil {
		return nil, errors.New("managed branding file not found")
	}
	if !strings.EqualFold(strings.TrimSpace(fileRow.Status), filemanager.FileStatusReady) {
		return nil, errors.New("managed branding file is not ready")
	}

	objectKey, declaredContentType, err := selectAuthEmailLogoVariant(fileRow)
	if err != nil {
		return nil, err
	}
	presigned, err := r.storage.PresignGetObject(ctx, objectKey, authEmailLogoPresignTTL)
	if err != nil {
		return nil, fmt.Errorf("presign managed branding object: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, presigned, nil)
	if err != nil {
		return nil, fmt.Errorf("build object request: %w", err)
	}
	resp, err := r.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("download logo object: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("object storage returned status %d", resp.StatusCode)
	}

	limited := io.LimitReader(resp.Body, authEmailLogoMaxBytes+1)
	body, err := io.ReadAll(limited)
	if err != nil {
		return nil, fmt.Errorf("read logo object body: %w", err)
	}
	if len(body) == 0 {
		return nil, errors.New("logo object body is empty")
	}
	if len(body) > authEmailLogoMaxBytes {
		return nil, errors.New("logo object exceeds max allowed size")
	}

	contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
	if contentType == "" {
		contentType = declaredContentType
	}
	if !isAuthEmailSafeImageContentType(contentType) {
		return nil, errors.New("managed branding asset is not email-safe")
	}

	return &queue.EmailAttachment{
		Filename:    authEmailLogoFilename,
		ContentType: normalizeAuthEmailImageContentType(contentType),
		ContentID:   authEmailLogoCID,
		Content:     body,
		Inline:      true,
	}, nil
}

func selectAuthEmailLogoVariant(fileRow *filemanager.File) (string, string, error) {
	if fileRow == nil {
		return "", "", errors.New("managed branding file is missing")
	}
	if fileRow.ThumbnailObjectKey != nil {
		thumbKey := strings.TrimSpace(*fileRow.ThumbnailObjectKey)
		thumbType := strings.TrimSpace(authEmailPtrStringValue(fileRow.ThumbnailMimeType))
		if thumbKey != "" && isAuthEmailSafeImageContentType(thumbType) {
			return thumbKey, thumbType, nil
		}
	}
	originalKey := strings.TrimSpace(fileRow.ObjectKey)
	originalType := strings.TrimSpace(fileRow.MimeType)
	if originalKey != "" && isAuthEmailSafeImageContentType(originalType) {
		return originalKey, originalType, nil
	}
	return "", "", errors.New("managed branding file has no email-safe variant")
}

func isAuthEmailSafeImageContentType(contentType string) bool {
	contentType = normalizeAuthEmailImageContentType(contentType)
	switch contentType {
	case "image/png", "image/jpeg", "image/gif":
		return true
	default:
		return false
	}
}

func normalizeAuthEmailImageContentType(contentType string) string {
	contentType = strings.ToLower(strings.TrimSpace(contentType))
	if idx := strings.Index(contentType, ";"); idx >= 0 {
		contentType = strings.TrimSpace(contentType[:idx])
	}
	if contentType == "image/jpg" {
		return "image/jpeg"
	}
	return contentType
}

func authEmailPtrStringValue(v *string) string {
	if v == nil {
		return ""
	}
	return *v
}

type AuthEmailTemplateContent struct {
	Subject    string
	Greeting   string
	Title      string
	Message    []string
	Code       string
	CTALabel   string
	CTAURL     string
	HelpText   string
	FooterText string
}

func buildAuthEmailTemplate(ctx context.Context, resolver AuthEmailBrandingResolver, content AuthEmailTemplateContent) BuiltAuthEmail {
	subject := strings.TrimSpace(content.Subject)
	greeting := strings.TrimSpace(content.Greeting)
	title := strings.TrimSpace(content.Title)
	ctaLabel := strings.TrimSpace(content.CTALabel)
	ctaURL := strings.TrimSpace(content.CTAURL)
	helpText := strings.TrimSpace(content.HelpText)
	footerText := strings.TrimSpace(content.FooterText)
	code := strings.TrimSpace(content.Code)

	messageParts := make([]string, 0, len(content.Message))
	for i := range content.Message {
		part := strings.TrimSpace(content.Message[i])
		if part == "" {
			continue
		}
		messageParts = append(messageParts, part)
	}

	textLines := make([]string, 0, len(messageParts)+10)
	if greeting != "" {
		textLines = append(textLines, greeting, "")
	}
	textLines = append(textLines, title, "")
	textLines = append(textLines, messageParts...)
	if code != "" {
		textLines = append(textLines, code)
	}
	if ctaURL != "" {
		textLines = append(textLines, "", ctaLabel+":", ctaURL)
	}
	if helpText != "" {
		textLines = append(textLines, "", helpText)
	}
	if footerText != "" {
		textLines = append(textLines, "", footerText)
	}

	branding := AuthEmailBranding{CompanyName: defaultEmailBrandingCompanyName}
	if resolver != nil {
		branding = resolver.ResolveEmailBranding(ctx)
		if strings.TrimSpace(branding.CompanyName) == "" {
			branding.CompanyName = defaultEmailBrandingCompanyName
		}
	}

	var attachments []queue.EmailAttachment
	if branding.LogoAttachment != nil {
		attachments = append(attachments, cloneAuthEmailAttachment(*branding.LogoAttachment))
	}

	return BuiltAuthEmail{
		Subject:     subject,
		TextBody:    strings.Join(textLines, "\n"),
		HTMLBody:    renderAuthEmailHTML(branding, AuthEmailTemplateContent{Subject: subject, Greeting: greeting, Title: title, Message: messageParts, Code: code, CTALabel: ctaLabel, CTAURL: ctaURL, HelpText: helpText, FooterText: footerText}),
		Attachments: attachments,
	}
}

func cloneAuthEmailAttachment(attachment queue.EmailAttachment) queue.EmailAttachment {
	attachment.Content = append([]byte(nil), attachment.Content...)
	return attachment
}

// emailFontStack is the cross-client system font stack used in all email cells.
const emailFontStack = `-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif`

// emailCell writes an opening <tr><td> with the given inline style.
func emailCell(b *strings.Builder, style string) {
	b.WriteString(`<tr><td style="`)
	b.WriteString(style)
	b.WriteString(`">`)
}

func emailCellClose(b *strings.Builder) {
	b.WriteString(`</td></tr>`)
}

// emailSpacer writes a zero-content spacer row.
func emailSpacer(b *strings.Builder, height string) {
	b.WriteString(`<tr><td style="height:`)
	b.WriteString(height)
	b.WriteString(`;font-size:0;line-height:0;">&nbsp;</td></tr>`)
}

// emailDivider writes a 1px horizontal rule as a nested table (Outlook-safe).
func emailDivider(b *strings.Builder) {
	b.WriteString(`<tr><td style="padding:0 32px;">`)
	b.WriteString(`<table role="presentation" width="100%" cellspacing="0" cellpadding="0">`)
	b.WriteString(`<tr><td style="height:1px;background-color:#e4e4e7;font-size:0;line-height:0;">&nbsp;</td></tr>`)
	b.WriteString(`</table></td></tr>`)
}

func renderAuthEmailHTML(branding AuthEmailBranding, content AuthEmailTemplateContent) string {
	subject := html.EscapeString(strings.TrimSpace(content.Subject))
	greeting := html.EscapeString(strings.TrimSpace(content.Greeting))
	title := html.EscapeString(strings.TrimSpace(content.Title))
	ctaLabel := html.EscapeString(strings.TrimSpace(content.CTALabel))
	ctaURL := html.EscapeString(strings.TrimSpace(content.CTAURL))
	helpText := html.EscapeString(strings.TrimSpace(content.HelpText))
	footerText := html.EscapeString(strings.TrimSpace(content.FooterText))
	code := html.EscapeString(strings.TrimSpace(content.Code))
	companyName := html.EscapeString(strings.TrimSpace(branding.CompanyName))
	logoContentID := html.EscapeString(strings.TrimSpace(branding.LogoContentID))

	var b strings.Builder

	b.WriteString(`<!DOCTYPE html><html lang="en">`)
	b.WriteString(`<head>`)
	b.WriteString(`<meta charset="UTF-8">`)
	b.WriteString(`<meta name="viewport" content="width=device-width,initial-scale=1.0">`)
	b.WriteString(`<meta http-equiv="X-UA-Compatible" content="IE=edge">`)
	b.WriteString(`<title>`)
	b.WriteString(subject)
	b.WriteString(`</title>`)
	b.WriteString(`<style>`)
	b.WriteString(`@media only screen and (max-width:620px){`)
	b.WriteString(`.em-cell{padding-left:20px!important;padding-right:20px!important;}`)
	b.WriteString(`.em-cta a{display:block!important;text-align:center!important;}`)
	b.WriteString(`}`)
	b.WriteString(`</style>`)
	b.WriteString(`</head>`)

	b.WriteString(`<body style="margin:0;padding:0;background-color:#ffffff;`)
	b.WriteString(`-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;">`)

	b.WriteString(`<div style="display:none;max-height:0;overflow:hidden;mso-hide:all;font-size:1px;color:#ffffff;">`)
	b.WriteString(subject)
	b.WriteString(strings.Repeat(" &zwnj;&nbsp;", 50))
	b.WriteString(`</div>`)

	b.WriteString(`<table role="presentation" width="100%" cellspacing="0" cellpadding="0" bgcolor="#ffffff">`)
	b.WriteString(`<tr><td align="center" style="padding:0;">`)

	b.WriteString(`<table role="presentation" cellspacing="0" cellpadding="0" style="width:100%;max-width:600px;">`)
	b.WriteString(`<tr><td style="height:4px;background-color:#18181b;font-size:0;line-height:0;">&nbsp;</td></tr>`)
	b.WriteString(`</table>`)

	b.WriteString(`<table role="presentation" cellspacing="0" cellpadding="0" style="width:100%;max-width:600px;">`)

	emailSpacer(&b, "44px")

	b.WriteString(`<tr><td class="em-cell" align="left" style="padding:0 32px 36px;">`)
	if logoContentID != "" {
		b.WriteString(`<img src="cid:`)
		b.WriteString(logoContentID)
		b.WriteString(`" alt="`)
		b.WriteString(companyName)
		b.WriteString(`" height="36" style="display:block;height:36px;width:auto;max-width:200px;border:0;outline:none;text-decoration:none;">`)
	} else {
		b.WriteString(`<span style="font-size:16px;font-weight:700;color:#18181b;font-family:`)
		b.WriteString(emailFontStack)
		b.WriteString(`;letter-spacing:-0.3px;">`)
		b.WriteString(companyName)
		b.WriteString(`</span>`)
	}
	b.WriteString(`</td></tr>`)

	emailDivider(&b)
	emailSpacer(&b, "40px")

	if greeting != "" {
		b.WriteString(`<tr><td class="em-cell" style="padding:0 32px 6px;font-size:14px;line-height:1.6;color:#71717a;font-family:`)
		b.WriteString(emailFontStack)
		b.WriteString(`;">`)
		b.WriteString(greeting)
		b.WriteString(`</td></tr>`)
	}

	b.WriteString(`<tr><td class="em-cell" style="padding:0 32px 20px;font-size:26px;line-height:1.25;font-weight:700;`)
	b.WriteString(`letter-spacing:-0.5px;color:#18181b;font-family:`)
	b.WriteString(emailFontStack)
	b.WriteString(`;">`)
	b.WriteString(title)
	b.WriteString(`</td></tr>`)

	for i := range content.Message {
		msg := html.EscapeString(strings.TrimSpace(content.Message[i]))
		if msg == "" {
			continue
		}
		b.WriteString(`<tr><td class="em-cell" style="padding:0 32px 12px;font-size:15px;line-height:1.75;color:#3f3f46;font-family:`)
		b.WriteString(emailFontStack)
		b.WriteString(`;">`)
		b.WriteString(msg)
		b.WriteString(`</td></tr>`)
	}
	if code != "" {
		b.WriteString(`<tr><td class="em-cell" style="padding:4px 32px 16px;">`)
		b.WriteString(`<span style="display:inline-block;background-color:#f4f4f5;border:1px solid #e4e4e7;`)
		b.WriteString(`border-radius:10px;padding:12px 16px;font-size:28px;line-height:1;letter-spacing:6px;`)
		b.WriteString(`color:#111827;font-weight:700;font-family:`)
		b.WriteString(emailFontStack)
		b.WriteString(`;">`)
		b.WriteString(code)
		b.WriteString(`</span></td></tr>`)
	}

	if ctaURL != "" && ctaLabel != "" {
		emailSpacer(&b, "20px")

		b.WriteString(`<tr><td class="em-cell em-cta" align="left" style="padding:0 32px 16px;">`)
		b.WriteString(`<!--[if mso]>`)
		b.WriteString(`<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"`)
		b.WriteString(` href="`)
		b.WriteString(ctaURL)
		b.WriteString(`" style="height:44px;v-text-anchor:middle;width:180px;" arcsize="18%" stroke="f" fillcolor="#18181b">`)
		b.WriteString(`<w:anchorlock/>`)
		b.WriteString(`<center style="color:#ffffff;font-family:sans-serif;font-size:14px;font-weight:700;">`)
		b.WriteString(ctaLabel)
		b.WriteString(`</center></v:roundrect><![endif]-->`)
		b.WriteString(`<!--[if !mso]><!-->`)
		b.WriteString(`<a href="`)
		b.WriteString(ctaURL)
		b.WriteString(`" target="_blank" style="display:inline-block;background-color:#18181b;color:#ffffff;`)
		b.WriteString(`font-family:`)
		b.WriteString(emailFontStack)
		b.WriteString(`;font-size:14px;font-weight:600;letter-spacing:0.1px;line-height:1;`)
		b.WriteString(`text-decoration:none;padding:13px 26px;border-radius:8px;`)
		b.WriteString(`-webkit-text-size-adjust:none;">`)
		b.WriteString(ctaLabel)
		b.WriteString(`</a>`)
		b.WriteString(`<!--<![endif]-->`)
		b.WriteString(`</td></tr>`)

		b.WriteString(`<tr><td class="em-cell" style="padding:0 32px 4px;font-size:12px;line-height:1.6;color:#a1a1aa;font-family:`)
		b.WriteString(emailFontStack)
		b.WriteString(`;">Or copy and paste this link:</td></tr>`)
		b.WriteString(`<tr><td class="em-cell" style="padding:0 32px 36px;font-size:12px;line-height:1.6;color:#71717a;`)
		b.WriteString(`word-break:break-all;font-family:`)
		b.WriteString(emailFontStack)
		b.WriteString(`;">`)
		b.WriteString(ctaURL)
		b.WriteString(`</td></tr>`)
	}

	emailDivider(&b)
	emailSpacer(&b, "24px")

	if helpText != "" {
		b.WriteString(`<tr><td class="em-cell" style="padding:0 32px 32px;font-size:13px;line-height:1.7;color:#71717a;font-family:`)
		b.WriteString(emailFontStack)
		b.WriteString(`;">`)
		b.WriteString(helpText)
		b.WriteString(`</td></tr>`)
	}

	b.WriteString(`<tr><td class="em-cell" style="padding:20px 32px 0;border-top:1px solid #e4e4e7;">`)
	b.WriteString(`<span style="font-size:12px;line-height:1.7;color:#a1a1aa;font-family:`)
	b.WriteString(emailFontStack)
	b.WriteString(`;">`)
	b.WriteString(companyName)
	b.WriteString(`</span>`)
	if footerText != "" {
		b.WriteString(`<br><span style="font-size:12px;line-height:1.7;color:#a1a1aa;font-family:`)
		b.WriteString(emailFontStack)
		b.WriteString(`;">`)
		b.WriteString(footerText)
		b.WriteString(`</span>`)
	}
	b.WriteString(`</td></tr>`)

	emailSpacer(&b, "48px")
	b.WriteString(`</table>`)
	b.WriteString(`</td></tr>`)
	b.WriteString(`</table>`)
	b.WriteString(`</body></html>`)
	return b.String()
}

func formatEmailTTLMinutes(ttl time.Duration, fallback int) int {
	minutes := int(ttl.Minutes())
	if minutes < 1 {
		return fallback
	}
	return minutes
}

func buildPasswordResetEmail(ctx context.Context, resolver AuthEmailBrandingResolver, userName string, link string, ttl time.Duration) BuiltAuthEmail {
	minutes := formatEmailTTLMinutes(ttl, 20)
	greeting := ""
	if name := strings.TrimSpace(userName); name != "" {
		greeting = "Hi " + name + ","
	}
	return buildAuthEmailTemplate(ctx, resolver, AuthEmailTemplateContent{
		Subject:  "Reset your password",
		Greeting: greeting,
		Title:    "Reset your password",
		Message: []string{
			"We received a request to reset your password.",
			"Click the button below to choose a new password. This link expires in " + strconv.Itoa(minutes) + " minutes and can only be used once.",
		},
		CTALabel:   "Reset Password",
		CTAURL:     link,
		HelpText:   "If you did not request this, you can safely ignore this email. Your password will remain unchanged.",
		FooterText: "For your security, never share this link with anyone.",
	})
}

func buildSecurityPINResetEmail(ctx context.Context, resolver AuthEmailBrandingResolver, userName string, link string, ttl time.Duration) BuiltAuthEmail {
	minutes := formatEmailTTLMinutes(ttl, 20)
	greeting := ""
	if name := strings.TrimSpace(userName); name != "" {
		greeting = "Hi " + name + ","
	}
	return buildAuthEmailTemplate(ctx, resolver, AuthEmailTemplateContent{
		Subject:  "Reset your security PIN",
		Greeting: greeting,
		Title:    "Reset your security PIN",
		Message: []string{
			"We received a request to reset your security PIN.",
			"Click the button below to set a new 6-digit PIN. This link expires in " + strconv.Itoa(minutes) + " minutes and can only be used once.",
		},
		CTALabel:   "Reset Security PIN",
		CTAURL:     link,
		HelpText:   "If you did not request this, you can safely ignore this email. Your PIN will remain unchanged.",
		FooterText: "For your security, never share this link or your PIN with anyone.",
	})
}

func buildLoginOTPEmail(ctx context.Context, resolver AuthEmailBrandingResolver, userName, otpCode string, ttl time.Duration) BuiltAuthEmail {
	minutes := formatEmailTTLMinutes(ttl, 10)
	greeting := ""
	if name := strings.TrimSpace(userName); name != "" {
		greeting = "Hi " + name + ","
	}
	return buildAuthEmailTemplate(ctx, resolver, AuthEmailTemplateContent{
		Subject:  "Your login verification code",
		Greeting: greeting,
		Title:    "Verify your login",
		Message: []string{
			"Use this one-time code to complete your login:",
			"This code expires in " + strconv.Itoa(minutes) + " minutes.",
		},
		Code:       strings.TrimSpace(otpCode),
		HelpText:   "If this wasn't you, change your password immediately and contact support.",
		FooterText: "For your security, never share this code with anyone.",
	})
}
