package notificationimp

import (
	"context"
	"errors"
	"testing"

	"github.com/tech/sendico/notification/interface/api/localizer"
	mmail "github.com/tech/sendico/notification/internal/server/notificationimp/mail/messagebuilder"
	"github.com/tech/sendico/pkg/db/storable"
	"github.com/tech/sendico/pkg/domainprovider"
	mlogger "github.com/tech/sendico/pkg/mlogger/factory"
	"github.com/tech/sendico/pkg/model"
	"go.mongodb.org/mongo-driver/v2/bson"
)

// Mock implementations

type mockMailClient struct {
	sendFunc        func(r mmail.MailBuilder) error
	mailBuilderFunc func() mmail.MailBuilder
	sentMessages    []mockSentMessage
}

type mockSentMessage struct {
	accountID  string
	templateID string
	locale     string
	recipients []string
	data       map[string]string
}

func (m *mockMailClient) Send(r mmail.MailBuilder) error {
	if m.sendFunc != nil {
		return m.sendFunc(r)
	}
	// Record the message for verification
	msg, _ := r.Build()
	if msg != nil {
		sent := mockSentMessage{
			accountID:  msg.AccountID(),
			templateID: msg.TemplateID(),
			locale:     msg.Locale(),
			recipients: msg.Recipients(),
			data:       make(map[string]string),
		}
		// Extract string parameters
		for k, v := range msg.Parameters() {
			if str, ok := v.(string); ok {
				sent.data[k] = str
			}
		}
		m.sentMessages = append(m.sentMessages, sent)
	}
	return nil
}

func (m *mockMailClient) MailBuilder() mmail.MailBuilder {
	if m.mailBuilderFunc != nil {
		return m.mailBuilderFunc()
	}
	return &mockMailBuilder{
		accountID:  "",
		templateID: "",
		locale:     "",
		recipients: []string{},
		data:       make(map[string]string),
	}
}

type mockMailBuilder struct {
	accountID  string
	templateID string
	locale     string
	recipients []string
	buttonLink string
	data       map[string]string
}

func (m *mockMailBuilder) SetAccountID(accountID string) mmail.MailBuilder {
	m.accountID = accountID
	return m
}

func (m *mockMailBuilder) SetTemplateID(templateID string) mmail.MailBuilder {
	m.templateID = templateID
	return m
}

func (m *mockMailBuilder) SetLocale(locale string) mmail.MailBuilder {
	m.locale = locale
	return m
}

func (m *mockMailBuilder) AddRecipient(recipientName, recipient string) mmail.MailBuilder {
	m.recipients = append(m.recipients, recipient)
	return m
}

func (m *mockMailBuilder) AddButton(link string) mmail.MailBuilder {
	m.buttonLink = link
	return m
}

func (m *mockMailBuilder) AddData(key, value string) mmail.MailBuilder {
	m.data[key] = value
	return m
}

func (m *mockMailBuilder) Build() (mmail.Message, error) {
	if len(m.recipients) == 0 {
		return nil, errors.New("recipient not set")
	}
	return &mockMessage{
		accountID:  m.accountID,
		templateID: m.templateID,
		locale:     m.locale,
		recipients: m.recipients,
		parameters: convertToAnyMap(m.data),
	}, nil
}

type mockMessage struct {
	accountID  string
	templateID string
	locale     string
	recipients []string
	parameters map[string]any
}

func (m *mockMessage) AccountID() string          { return m.accountID }
func (m *mockMessage) TemplateID() string         { return m.templateID }
func (m *mockMessage) Locale() string             { return m.locale }
func (m *mockMessage) Recipients() []string       { return m.recipients }
func (m *mockMessage) Parameters() map[string]any { return m.parameters }
func (m *mockMessage) Body(l localizer.Localizer, dp domainprovider.DomainProvider) (string, error) {
	return "", nil
}

func convertToAnyMap(m map[string]string) map[string]any {
	result := make(map[string]any)
	for k, v := range m {
		result[k] = v
	}
	return result
}

type mockDomainProvider struct {
	getFullLinkFunc func(linkElem ...string) (string, error)
}

func (m *mockDomainProvider) GetFullLink(linkElem ...string) (string, error) {
	if m.getFullLinkFunc != nil {
		return m.getFullLinkFunc(linkElem...)
	}
	return "https://example.com/link", nil
}

func (m *mockDomainProvider) GetAPILink(linkElem ...string) (string, error) {
	return "https://api.example.com/link", nil
}

// Tests for onAccount handler

func TestOnAccount_ValidAccount_SendsWelcomeEmail(t *testing.T) {
	mockClient := &mockMailClient{}
	mockDP := &mockDomainProvider{}

	api := &NotificationAPI{
		logger: mlogger.NewLogger(true),
		client: mockClient,
		dp:     mockDP,
	}

	account := &model.Account{
		AccountPublic: model.AccountPublic{
			AccountBase: model.AccountBase{
				Base: storable.Base{
					ID: bson.NewObjectID(),
				},
				Describable: model.Describable{
					Name: "Test User",
				},
			},
			UserDataBase: model.UserDataBase{
				Login:  "user@example.com",
				Locale: "en-US",
			},
		},
	}

	err := api.onAccount(context.Background(), account, "test-verify-token")

	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	if len(mockClient.sentMessages) != 1 {
		t.Fatalf("Expected 1 message sent, got %d", len(mockClient.sentMessages))
	}

	sent := mockClient.sentMessages[0]
	if sent.templateID != "welcome" {
		t.Errorf("Expected template 'welcome', got '%s'", sent.templateID)
	}
	if sent.locale != "en-US" {
		t.Errorf("Expected locale 'en-US', got '%s'", sent.locale)
	}
	if len(sent.recipients) != 1 || sent.recipients[0] != "user@example.com" {
		t.Errorf("Expected recipient 'user@example.com', got %v", sent.recipients)
	}
}

func TestOnAccount_LinkGenerationFails_ReturnsError(t *testing.T) {
	mockClient := &mockMailClient{}
	mockDP := &mockDomainProvider{
		getFullLinkFunc: func(linkElem ...string) (string, error) {
			return "", errors.New("link generation failed")
		},
	}

	api := &NotificationAPI{
		logger: mlogger.NewLogger(true),
		client: mockClient,
		dp:     mockDP,
	}

	account := &model.Account{
		AccountPublic: model.AccountPublic{
			AccountBase: model.AccountBase{
				Base: storable.Base{
					ID: bson.NewObjectID(),
				},
				Describable: model.Describable{
					Name: "Test User",
				},
			},
			UserDataBase: model.UserDataBase{
				Login:  "user@example.com",
				Locale: "en-US",
			},
		},
	}

	err := api.onAccount(context.Background(), account, "test-verify-token")

	if err == nil {
		t.Fatal("Expected error from link generation failure")
	}

	if len(mockClient.sentMessages) != 0 {
		t.Error("No message should be sent when link generation fails")
	}
}

func TestOnAccount_SendFails_ReturnsError(t *testing.T) {
	mockClient := &mockMailClient{
		sendFunc: func(r mmail.MailBuilder) error {
			return errors.New("send failed")
		},
	}
	mockDP := &mockDomainProvider{}

	api := &NotificationAPI{
		logger: mlogger.NewLogger(true),
		client: mockClient,
		dp:     mockDP,
	}

	account := &model.Account{
		AccountPublic: model.AccountPublic{
			AccountBase: model.AccountBase{
				Base: storable.Base{
					ID: bson.NewObjectID(),
				},
				Describable: model.Describable{
					Name: "Test User",
				},
			},
			UserDataBase: model.UserDataBase{
				Login:  "user@example.com",
				Locale: "en-US",
			},
		},
	}

	err := api.onAccount(context.Background(), account, "test-verify-token")

	if err == nil {
		t.Fatal("Expected error from send failure")
	}
}

// Tests for onInvitation handler

func TestOnInvitation_ValidInvitation_SendsInvitationEmail(t *testing.T) {
	mockClient := &mockMailClient{}
	mockDP := &mockDomainProvider{}

	api := &NotificationAPI{
		logger: mlogger.NewLogger(true),
		client: mockClient,
		dp:     mockDP,
	}

	account := &model.Account{
		AccountPublic: model.AccountPublic{
			AccountBase: model.AccountBase{
				Base: storable.Base{
					ID: bson.NewObjectID(),
				},
				Describable: model.Describable{
					Name: "Inviter User",
				},
			},
			UserDataBase: model.UserDataBase{
				Locale: "en-US",
			},
		},
	}

	invitationID := bson.NewObjectID()
	invitation := &model.Invitation{}
	invitation.ID = invitationID
	invitation.Content.Email = "invitee@example.com"
	invitation.Content.Name = "Invitee Name"

	err := api.onInvitation(context.Background(), account, invitation)

	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	if len(mockClient.sentMessages) != 1 {
		t.Fatalf("Expected 1 message sent, got %d", len(mockClient.sentMessages))
	}

	sent := mockClient.sentMessages[0]
	if sent.templateID != "invitation" {
		t.Errorf("Expected template 'invitation', got '%s'", sent.templateID)
	}
	if sent.locale != "en-US" {
		t.Errorf("Expected locale 'en-US', got '%s'", sent.locale)
	}
	if len(sent.recipients) != 1 || sent.recipients[0] != "invitee@example.com" {
		t.Errorf("Expected recipient 'invitee@example.com', got %v", sent.recipients)
	}
	if sent.data["InviterName"] != "Inviter User" {
		t.Errorf("Expected InviterName 'Inviter User', got '%s'", sent.data["InviterName"])
	}
	if sent.data["Name"] != "Invitee Name" {
		t.Errorf("Expected Name 'Invitee Name', got '%s'", sent.data["Name"])
	}
}

func TestOnInvitation_LinkGenerationFails_ReturnsError(t *testing.T) {
	mockClient := &mockMailClient{}
	mockDP := &mockDomainProvider{
		getFullLinkFunc: func(linkElem ...string) (string, error) {
			return "", errors.New("link generation failed")
		},
	}

	api := &NotificationAPI{
		logger: mlogger.NewLogger(true),
		client: mockClient,
		dp:     mockDP,
	}

	account := &model.Account{
		AccountPublic: model.AccountPublic{
			AccountBase: model.AccountBase{
				Base: storable.Base{
					ID: bson.NewObjectID(),
				},
				Describable: model.Describable{
					Name: "Inviter User",
				},
			},
			UserDataBase: model.UserDataBase{
				Locale: "en-US",
			},
		},
	}

	invitationID := bson.NewObjectID()
	invitation := &model.Invitation{}
	invitation.ID = invitationID
	invitation.Content.Email = "invitee@example.com"
	invitation.Content.Name = "Invitee Name"

	err := api.onInvitation(context.Background(), account, invitation)

	if err == nil {
		t.Fatal("Expected error from link generation failure")
	}

	if len(mockClient.sentMessages) != 0 {
		t.Error("No message should be sent when link generation fails")
	}
}

// Tests for onPasswordReset handler

func TestOnPasswordReset_ValidReset_SendsResetEmail(t *testing.T) {
	mockClient := &mockMailClient{}
	mockDP := &mockDomainProvider{}

	api := &NotificationAPI{
		logger: mlogger.NewLogger(true),
		client: mockClient,
		dp:     mockDP,
	}

	account := &model.Account{
		AccountPublic: model.AccountPublic{
			AccountBase: model.AccountBase{
				Base: storable.Base{
					ID: bson.NewObjectID(),
				},
				Describable: model.Describable{
					Name: "Test User",
				},
			},
			UserDataBase: model.UserDataBase{
				Login:  "user@example.com",
				Locale: "en-US",
			},
		},
	}

	resetToken := "reset-token-123"

	err := api.onPasswordReset(context.Background(), account, resetToken)

	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	if len(mockClient.sentMessages) != 1 {
		t.Fatalf("Expected 1 message sent, got %d", len(mockClient.sentMessages))
	}

	sent := mockClient.sentMessages[0]
	if sent.templateID != "reset-password" {
		t.Errorf("Expected template 'reset-password', got '%s'", sent.templateID)
	}
	if sent.locale != "en-US" {
		t.Errorf("Expected locale 'en-US', got '%s'", sent.locale)
	}
	if len(sent.recipients) != 1 || sent.recipients[0] != "user@example.com" {
		t.Errorf("Expected recipient 'user@example.com', got %v", sent.recipients)
	}
	if sent.data["URL"] == "" {
		t.Error("Expected URL parameter to be set")
	}
}

func TestOnPasswordReset_LinkGenerationFails_ReturnsError(t *testing.T) {
	mockClient := &mockMailClient{}
	mockDP := &mockDomainProvider{
		getFullLinkFunc: func(linkElem ...string) (string, error) {
			return "", errors.New("link generation failed")
		},
	}

	api := &NotificationAPI{
		logger: mlogger.NewLogger(true),
		client: mockClient,
		dp:     mockDP,
	}

	account := &model.Account{
		AccountPublic: model.AccountPublic{
			AccountBase: model.AccountBase{
				Base: storable.Base{
					ID: bson.NewObjectID(),
				},
				Describable: model.Describable{
					Name: "Test User",
				},
			},
			UserDataBase: model.UserDataBase{
				Login:  "user@example.com",
				Locale: "en-US",
			},
		},
	}

	err := api.onPasswordReset(context.Background(), account, "reset-token")

	if err == nil {
		t.Fatal("Expected error from link generation failure")
	}

	if len(mockClient.sentMessages) != 0 {
		t.Error("No message should be sent when link generation fails")
	}
}

func TestOnPasswordReset_SendFails_ReturnsError(t *testing.T) {
	mockClient := &mockMailClient{
		sendFunc: func(r mmail.MailBuilder) error {
			return errors.New("send failed")
		},
	}
	mockDP := &mockDomainProvider{}

	api := &NotificationAPI{
		logger: mlogger.NewLogger(true),
		client: mockClient,
		dp:     mockDP,
	}

	account := &model.Account{
		AccountPublic: model.AccountPublic{
			AccountBase: model.AccountBase{
				Base: storable.Base{
					ID: bson.NewObjectID(),
				},
				Describable: model.Describable{
					Name: "Test User",
				},
			},
			UserDataBase: model.UserDataBase{
				Login:  "user@example.com",
				Locale: "en-US",
			},
		},
	}

	err := api.onPasswordReset(context.Background(), account, "reset-token")

	if err == nil {
		t.Fatal("Expected error from send failure")
	}
}
