package email

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"log/slog"
	"net"
	"net/smtp"
	"strconv"
	"strings"
	"time"

	"github.com/shankar0123/certctl/internal/connector/notifier"
)

// Config represents the email notifier configuration.
type Config struct {
	SMTPHost    string `json:"smtp_host"`
	SMTPPort    int    `json:"smtp_port"`
	Username    string `json:"username"`
	Password    string `json:"password"`
	FromAddress string `json:"from_address"`
	UseTLS      bool   `json:"tls"`
}

// Connector implements the notifier.Connector interface for email notifications.
// It sends alert and event notifications via SMTP.
type Connector struct {
	config *Config
	logger *slog.Logger
}

// New creates a new email notifier with the given configuration and logger.
func New(config *Config, logger *slog.Logger) *Connector {
	return &Connector{
		config: config,
		logger: logger,
	}
}

// ValidateConfig checks that the SMTP server is reachable and credentials are valid.
// It attempts to connect to the SMTP server to verify connectivity.
func (c *Connector) ValidateConfig(ctx context.Context, rawConfig json.RawMessage) error {
	var cfg Config
	if err := json.Unmarshal(rawConfig, &cfg); err != nil {
		return fmt.Errorf("invalid email config: %w", err)
	}

	if cfg.SMTPHost == "" || cfg.SMTPPort == 0 || cfg.FromAddress == "" {
		return fmt.Errorf("email smtp_host, smtp_port, and from_address are required")
	}

	c.logger.Info("validating email configuration",
		"smtp_host", cfg.SMTPHost,
		"smtp_port", cfg.SMTPPort)

	// Test SMTP connectivity with timeout
	addr := net.JoinHostPort(cfg.SMTPHost, strconv.Itoa(cfg.SMTPPort))
	conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
	if err != nil {
		return fmt.Errorf("failed to reach SMTP server %s: %w", addr, err)
	}
	defer conn.Close()

	c.config = &cfg
	c.logger.Info("email configuration validated")
	return nil
}

// SendAlert sends an alert notification via SMTP.
// It formats the alert as an email message and sends it to the recipient.
func (c *Connector) SendAlert(ctx context.Context, alert notifier.Alert) error {
	c.logger.Info("sending email alert",
		"alert_id", alert.ID,
		"severity", alert.Severity,
		"recipient", alert.Recipient)

	// Format email subject and body
	subject := fmt.Sprintf("[%s] %s", strings.ToUpper(alert.Severity), alert.Subject)
	body := c.formatAlertBody(alert)

	// Send email
	if err := c.sendEmail(ctx, alert.Recipient, subject, body); err != nil {
		c.logger.Error("failed to send alert email",
			"alert_id", alert.ID,
			"error", err)
		return fmt.Errorf("failed to send alert email: %w", err)
	}

	c.logger.Info("alert email sent successfully",
		"alert_id", alert.ID,
		"recipient", alert.Recipient)
	return nil
}

// SendEvent sends an event notification via SMTP.
// It formats the event as an email message and sends it to the recipient.
func (c *Connector) SendEvent(ctx context.Context, event notifier.Event) error {
	c.logger.Info("sending email event",
		"event_id", event.ID,
		"event_type", event.Type,
		"recipient", event.Recipient)

	// Format email subject and body
	subject := fmt.Sprintf("[Event] %s", event.Subject)
	body := c.formatEventBody(event)

	// Send email
	if err := c.sendEmail(ctx, event.Recipient, subject, body); err != nil {
		c.logger.Error("failed to send event email",
			"event_id", event.ID,
			"error", err)
		return fmt.Errorf("failed to send event email: %w", err)
	}

	c.logger.Info("event email sent successfully",
		"event_id", event.ID,
		"recipient", event.Recipient)
	return nil
}

// sendEmail sends an email message using the configured SMTP server.
// It handles both TLS and plain authentication modes.
func (c *Connector) sendEmail(ctx context.Context, to, subject, body string) error {
	addr := net.JoinHostPort(c.config.SMTPHost, strconv.Itoa(c.config.SMTPPort))

	// Connect to SMTP server
	var auth smtp.Auth
	if c.config.Username != "" && c.config.Password != "" {
		auth = smtp.PlainAuth("", c.config.Username, c.config.Password, c.config.SMTPHost)
	}

	var conn net.Conn
	var err error

	if c.config.UseTLS {
		// Connect with TLS
		tlsConfig := &tls.Config{
			ServerName: c.config.SMTPHost,
		}
		conn, err = tls.Dial("tcp", addr, tlsConfig)
		if err != nil {
			return fmt.Errorf("failed to connect via TLS: %w", err)
		}
	} else {
		// Connect without TLS
		conn, err = net.Dial("tcp", addr)
		if err != nil {
			return fmt.Errorf("failed to connect: %w", err)
		}
	}
	defer conn.Close()

	// Create SMTP client
	client, err := smtp.NewClient(conn, c.config.SMTPHost)
	if err != nil {
		return fmt.Errorf("failed to create SMTP client: %w", err)
	}
	defer client.Close()

	// Authenticate if credentials provided
	if auth != nil {
		if err := client.Auth(auth); err != nil {
			return fmt.Errorf("SMTP authentication failed: %w", err)
		}
	}

	// Send email
	if err := client.Mail(c.config.FromAddress); err != nil {
		return fmt.Errorf("failed to set sender: %w", err)
	}

	if err := client.Rcpt(to); err != nil {
		return fmt.Errorf("failed to set recipient: %w", err)
	}

	wc, err := client.Data()
	if err != nil {
		return fmt.Errorf("failed to get data writer: %w", err)
	}
	defer wc.Close()

	// Format and write email headers and body
	message := c.formatEmailMessage(c.config.FromAddress, to, subject, body)
	if _, err := wc.Write(message); err != nil {
		return fmt.Errorf("failed to write message: %w", err)
	}

	if err := client.Quit(); err != nil {
		return fmt.Errorf("failed to quit SMTP: %w", err)
	}

	return nil
}

// sendHTMLEmail sends an HTML email message using the configured SMTP server.
// Used by the digest service for rich HTML digest emails.
func (c *Connector) sendHTMLEmail(ctx context.Context, to, subject, htmlBody string) error {
	addr := net.JoinHostPort(c.config.SMTPHost, strconv.Itoa(c.config.SMTPPort))

	var auth smtp.Auth
	if c.config.Username != "" && c.config.Password != "" {
		auth = smtp.PlainAuth("", c.config.Username, c.config.Password, c.config.SMTPHost)
	}

	var conn net.Conn
	var err error

	if c.config.UseTLS {
		tlsConfig := &tls.Config{
			ServerName: c.config.SMTPHost,
		}
		conn, err = tls.Dial("tcp", addr, tlsConfig)
		if err != nil {
			return fmt.Errorf("failed to connect via TLS: %w", err)
		}
	} else {
		conn, err = net.Dial("tcp", addr)
		if err != nil {
			return fmt.Errorf("failed to connect: %w", err)
		}
	}
	defer conn.Close()

	client, err := smtp.NewClient(conn, c.config.SMTPHost)
	if err != nil {
		return fmt.Errorf("failed to create SMTP client: %w", err)
	}
	defer client.Close()

	if auth != nil {
		if err := client.Auth(auth); err != nil {
			return fmt.Errorf("SMTP authentication failed: %w", err)
		}
	}

	if err := client.Mail(c.config.FromAddress); err != nil {
		return fmt.Errorf("failed to set sender: %w", err)
	}

	if err := client.Rcpt(to); err != nil {
		return fmt.Errorf("failed to set recipient: %w", err)
	}

	wc, err := client.Data()
	if err != nil {
		return fmt.Errorf("failed to get data writer: %w", err)
	}
	defer wc.Close()

	message := c.formatHTMLEmailMessage(c.config.FromAddress, to, subject, htmlBody)
	if _, err := wc.Write(message); err != nil {
		return fmt.Errorf("failed to write message: %w", err)
	}

	if err := client.Quit(); err != nil {
		return fmt.Errorf("failed to quit SMTP: %w", err)
	}

	return nil
}

// formatEmailMessage formats an email message with standard headers.
func (c *Connector) formatEmailMessage(from, to, subject, body string) []byte {
	message := fmt.Sprintf(
		"From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n%s",
		from,
		to,
		subject,
		time.Now().Format(time.RFC1123Z),
		body,
	)
	return []byte(message)
}

// formatHTMLEmailMessage formats an HTML email message with MIME headers.
func (c *Connector) formatHTMLEmailMessage(from, to, subject, htmlBody string) []byte {
	message := fmt.Sprintf(
		"From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/html; charset=utf-8\r\n\r\n%s",
		from,
		to,
		subject,
		time.Now().Format(time.RFC1123Z),
		htmlBody,
	)
	return []byte(message)
}

// formatAlertBody formats an alert notification as email body text.
func (c *Connector) formatAlertBody(alert notifier.Alert) string {
	body := fmt.Sprintf(`
Certificate Alert Notification
================================

Alert ID: %s
Type: %s
Severity: %s
Created: %s

Subject: %s

Message:
%s

%s
`, alert.ID, alert.Type, alert.Severity, alert.CreatedAt.Format(time.RFC3339), alert.Subject, alert.Message, c.formatMetadata(alert.Metadata))

	return body
}

// formatEventBody formats an event notification as email body text.
func (c *Connector) formatEventBody(event notifier.Event) string {
	certInfo := ""
	if event.CertificateID != nil {
		certInfo = fmt.Sprintf("Certificate ID: %s\n", *event.CertificateID)
	}

	body := fmt.Sprintf(`
Certificate Event Notification
================================

Event ID: %s
Type: %s
Created: %s

%sSubject: %s

Body:
%s

%s
`, event.ID, event.Type, event.CreatedAt.Format(time.RFC3339), certInfo, event.Subject, event.Body, c.formatMetadata(event.Metadata))

	return body
}

// formatMetadata formats metadata as a readable string.
func (c *Connector) formatMetadata(metadata map[string]string) string {
	if len(metadata) == 0 {
		return ""
	}

	metadataStr := "\nMetadata:\n"
	for key, value := range metadata {
		metadataStr += fmt.Sprintf("  %s: %s\n", key, value)
	}

	return metadataStr
}
