package mail

import (
	"bytes"
	"context"
	"crypto/tls"
	"fmt"
	"html/template"
	"net"
	"net/smtp"
	"os"
	"path/filepath"
	"server/internal/config"
	"strings"
	texttemplate "text/template"
	"time"
)

type Config struct {
	AppName           string
	Mode              string
	From              string
	DebugDir          string
	TemplatesDir      string
	FrontendBaseURL   string
	ResetPasswordPath string
	SMTP              SMTPConfig
}

type SMTPConfig struct {
	Host     string
	Port     int
	Username string
	Password string
}

type Message struct {
	To           string
	Subject      string
	Template     string
	TemplateData any
}

type MailService struct {
	cfg Config
}

type TemplateData struct {
	AppName    string
	UserName   string
	UserEmail  string
	ResetURL   string
	ResetToken string
}

// if service fail send admin allert instead a response to user or a simple response server error.
func New() (*MailService, error) {

	serverCfg, err := config.GetConfig()
	if err != nil {
		return nil, err
	}

	cfg := Config{
		AppName:           serverCfg.AppName,
		Mode:              serverCfg.Mail.Mode,
		From:              serverCfg.Mail.From,
		DebugDir:          serverCfg.Mail.DebugDir,
		TemplatesDir:      serverCfg.Mail.TemplatesDir,
		FrontendBaseURL:   serverCfg.Mail.FrontendBaseURL,
		ResetPasswordPath: serverCfg.Mail.ResetPasswordPath,
		SMTP: SMTPConfig{
			Host:     serverCfg.Mail.SMTP.Host,
			Port:     serverCfg.Mail.SMTP.Port,
			Username: serverCfg.Mail.SMTP.Username,
			Password: serverCfg.Mail.SMTP.Password,
		},
	}

	if cfg.Mode != "smtp" && cfg.Mode != "file" {
		return nil, fmt.Errorf("unsupported mail mode %q", cfg.Mode)
	}
	if cfg.From == "" {
		return nil, fmt.Errorf("mail sender is required")
	}
	if cfg.AppName == "" {
		cfg.AppName = "Application"
	}
	if cfg.TemplatesDir == "" {
		return nil, fmt.Errorf("mail templates directory is required")
	}
	if cfg.Mode == "file" {
		if cfg.DebugDir == "" {
			return nil, fmt.Errorf("mail debug directory is required")
		}
		if err := os.MkdirAll(cfg.DebugDir, 0o755); err != nil {
			return nil, fmt.Errorf("create mail debug directory: %w", err)
		}
	}
	if cfg.Mode == "smtp" {
		if cfg.SMTP.Host == "" || cfg.SMTP.Port <= 0 {
			return nil, fmt.Errorf("smtp host and port are required")
		}
	}
	return &MailService{cfg: cfg}, nil
}

func (s *MailService) Send(ctx context.Context, msg Message) error {
	htmlBody, textBody, err := s.renderBodies(msg.Template, msg.TemplateData)
	if err != nil {
		return err
	}

	rawMessage := buildMessage(s.cfg.From, msg.To, msg.Subject, textBody, htmlBody)
	switch s.cfg.Mode {
	case "smtp":
		return s.sendSMTP(ctx, msg.To, rawMessage)
	case "file":
		return s.writeDebugMail(msg.To, msg.Subject, rawMessage)
	default:
		return fmt.Errorf("unsupported mail mode %q", s.cfg.Mode)
	}
}

func (s *MailService) ResetLink(token string) string {
	base := strings.TrimRight(s.cfg.FrontendBaseURL, "/")
	path := s.cfg.ResetPasswordPath
	if path == "" {
		path = "/#reset-password"
	}
	if token == "" {
		return base + path
	}
	if base == "" {
		return path + "?token=" + token
	}
	return base + path + "?token=" + token
}

func (s *MailService) AppName() string {
	return s.cfg.AppName
}

func (s *MailService) renderBodies(templateName string, data any) (string, string, error) {
	htmlPath := filepath.Join(s.cfg.TemplatesDir, templateName+".html.tmpl")
	textPath := filepath.Join(s.cfg.TemplatesDir, templateName+".txt.tmpl")

	htmlTpl, err := template.ParseFiles(htmlPath)
	if err != nil {
		return "", "", fmt.Errorf("parse html template %s: %w", htmlPath, err)
	}
	textTpl, err := texttemplate.ParseFiles(textPath)
	if err != nil {
		return "", "", fmt.Errorf("parse text template %s: %w", textPath, err)
	}

	var htmlBuf bytes.Buffer
	if err := htmlTpl.Execute(&htmlBuf, data); err != nil {
		return "", "", fmt.Errorf("execute html template %s: %w", htmlPath, err)
	}
	var textBuf bytes.Buffer
	if err := textTpl.Execute(&textBuf, data); err != nil {
		return "", "", fmt.Errorf("execute text template %s: %w", textPath, err)
	}

	return htmlBuf.String(), textBuf.String(), nil
}

func buildMessage(from, to, subject, textBody, htmlBody string) []byte {
	boundary := fmt.Sprintf("mixed-%d", time.Now().UnixNano())
	headers := []string{
		"MIME-Version: 1.0",
		fmt.Sprintf("From: %s", from),
		fmt.Sprintf("To: %s", to),
		fmt.Sprintf("Subject: %s", subject),
		fmt.Sprintf("Content-Type: multipart/alternative; boundary=%q", boundary),
		"",
	}

	body := []string{
		fmt.Sprintf("--%s", boundary),
		"Content-Type: text/plain; charset=UTF-8",
		"Content-Transfer-Encoding: 8bit",
		"",
		textBody,
		fmt.Sprintf("--%s", boundary),
		"Content-Type: text/html; charset=UTF-8",
		"Content-Transfer-Encoding: 8bit",
		"",
		htmlBody,
		fmt.Sprintf("--%s--", boundary),
		"",
	}

	return []byte(strings.Join(append(headers, body...), "\r\n"))
}

func (s *MailService) sendSMTP(ctx context.Context, to string, raw []byte) error {
	addr := fmt.Sprintf("%s:%d", s.cfg.SMTP.Host, s.cfg.SMTP.Port)
	dialer := &net.Dialer{}
	conn, err := dialer.DialContext(ctx, "tcp", addr)
	if err != nil {
		return fmt.Errorf("dial smtp server: %w", err)
	}
	defer conn.Close()

	client, err := smtp.NewClient(conn, s.cfg.SMTP.Host)
	if err != nil {
		return fmt.Errorf("create smtp client: %w", err)
	}
	defer client.Close()

	if ok, _ := client.Extension("STARTTLS"); ok {
		tlsCfg := &tls.Config{ServerName: s.cfg.SMTP.Host}
		if err := client.StartTLS(tlsCfg); err != nil {
			return fmt.Errorf("starttls: %w", err)
		}
	}
	if s.cfg.SMTP.Username != "" {
		auth := smtp.PlainAuth("", s.cfg.SMTP.Username, s.cfg.SMTP.Password, s.cfg.SMTP.Host)
		if err := client.Auth(auth); err != nil {
			return fmt.Errorf("smtp auth: %w", err)
		}
	}
	if err := client.Mail(s.cfg.From); err != nil {
		return fmt.Errorf("smtp mail from: %w", err)
	}
	if err := client.Rcpt(to); err != nil {
		return fmt.Errorf("smtp rcpt to: %w", err)
	}
	wc, err := client.Data()
	if err != nil {
		return fmt.Errorf("smtp data: %w", err)
	}
	if _, err := wc.Write(raw); err != nil {
		_ = wc.Close()
		return fmt.Errorf("write smtp message: %w", err)
	}
	if err := wc.Close(); err != nil {
		return fmt.Errorf("close smtp message: %w", err)
	}
	if err := client.Quit(); err != nil {
		return fmt.Errorf("smtp quit: %w", err)
	}
	return nil
}

func (s *MailService) writeDebugMail(to, subject string, raw []byte) error {
	safeRecipient := strings.NewReplacer("@", "_at_", "/", "_", "\\", "_", ":", "_", " ", "_").Replace(to)
	filename := fmt.Sprintf("%d_%s.eml", time.Now().UnixNano(), safeRecipient)
	path := filepath.Join(s.cfg.DebugDir, filename)
	if err := os.WriteFile(path, raw, 0o644); err != nil {
		return fmt.Errorf("write debug mail %s: %w", path, err)
	}
	_ = subject
	return nil
}
