package service

import (
	"context"
	"encoding/json"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"time"

	"wucher/internal/shared/pkg/uuidv7"
	"wucher/internal/queue"
)

type MockEmailSender struct {
	dir string
}

type MockEmailRecord struct {
	StoredAt    time.Time         `json:"stored_at"`
	Provider    string            `json:"provider"`
	JobID       string            `json:"job_id"`
	MessageType string            `json:"message_type"`
	To          string            `json:"to"`
	CC          []string          `json:"cc,omitempty"`
	BCC         []string          `json:"bcc,omitempty"`
	ReplyTo     []string          `json:"reply_to,omitempty"`
	Subject     string            `json:"subject"`
	TextBody    string            `json:"text_body,omitempty"`
	HTMLBody    string            `json:"html_body,omitempty"`
	Tags        map[string]string `json:"tags,omitempty"`
	Metadata    map[string]string `json:"metadata,omitempty"`
	TraceID     string            `json:"trace_id,omitempty"`
	RequestID   string            `json:"request_id,omitempty"`
}

func NewMockEmailSender(dir string) *MockEmailSender {
	return &MockEmailSender{dir: strings.TrimSpace(dir)}
}

func (s *MockEmailSender) Configured() bool {
	return s != nil && strings.TrimSpace(s.dir) != ""
}

func (s *MockEmailSender) Send(ctx context.Context, job queue.EmailJob) (EmailDeliveryResult, error) {
	if !s.Configured() {
		return EmailDeliveryResult{}, errors.New("mock email sender is not configured")
	}
	job = job.Canonical()
	if err := job.Validate(); err != nil {
		return EmailDeliveryResult{}, err
	}
	select {
	case <-ctx.Done():
		return EmailDeliveryResult{}, ctx.Err()
	default:
	}

	if err := os.MkdirAll(s.dir, 0o755); err != nil {
		return EmailDeliveryResult{}, err
	}

	jobID := strings.TrimSpace(job.JobID)
	if jobID == "" {
		var err error
		jobID, err = uuidv7.String(uuidv7.MustNew())
		if err != nil {
			return EmailDeliveryResult{}, err
		}
	}
	filename := time.Now().UTC().Format("20060102T150405.000000000Z") + "_" + sanitizeFileName(job.NormalizedType()) + "_" + sanitizeFileName(jobID) + ".json"
	path := filepath.Join(s.dir, filename)

	record := MockEmailRecord{
		StoredAt:    time.Now().UTC(),
		Provider:    "mock",
		JobID:       jobID,
		MessageType: job.NormalizedType(),
		To:          job.To,
		CC:          append([]string(nil), job.CC...),
		BCC:         append([]string(nil), job.BCC...),
		ReplyTo:     append([]string(nil), job.ReplyTo...),
		Subject:     job.Subject,
		TextBody:    job.TextBodyValue(),
		HTMLBody:    job.HTMLBody,
		Tags:        cloneStringMap(job.Tags),
		Metadata:    cloneStringMap(job.Metadata),
		TraceID:     job.TraceID,
		RequestID:   job.RequestID,
	}
	payload, err := json.MarshalIndent(record, "", "  ")
	if err != nil {
		return EmailDeliveryResult{}, err
	}
	payload = append(payload, '\n')

	if err := os.WriteFile(path, payload, 0o600); err != nil {
		return EmailDeliveryResult{}, err
	}
	return EmailDeliveryResult{
		Provider:          "mock",
		ProviderMessageID: filename,
		RequestID:         path,
	}, nil
}

func sanitizeFileName(value string) string {
	value = strings.TrimSpace(value)
	if value == "" {
		return "email"
	}
	var b strings.Builder
	b.Grow(len(value))
	for _, r := range value {
		switch {
		case r >= 'a' && r <= 'z':
			b.WriteRune(r)
		case r >= 'A' && r <= 'Z':
			b.WriteRune(r)
		case r >= '0' && r <= '9':
			b.WriteRune(r)
		case r == '-', r == '_':
			b.WriteRune(r)
		default:
			b.WriteRune('_')
		}
	}
	return b.String()
}

func cloneStringMap(values map[string]string) map[string]string {
	if len(values) == 0 {
		return nil
	}
	out := make(map[string]string, len(values))
	for key, value := range values {
		out[key] = value
	}
	return out
}
