package smtpserver

import (
	"net"
	"net/smtp"
	"os"
	"strings"
	"testing"
	"time"

	"gopkg.in/ini.v1"
	"mailgoserver/internal/db"
	"mailgoserver/internal/dkim"
	"mailgoserver/internal/relay"
	"mailgoserver/internal/toolbox"
)

func newTestBackend(t *testing.T) *Backend {
	t.Helper()
	f, err := os.CreateTemp("", "smtp-test-*.db")
	if err != nil {
		t.Fatal(err)
	}
	f.Close()
	t.Cleanup(func() { os.Remove(f.Name()) })

	database, err := db.Open(f.Name())
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { database.Close() })

	if _, err := database.Exec(`INSERT INTO esrv_domains (domain_name, is_active, is_verified) VALUES ('example.com', 1, 1)`); err != nil {
		t.Fatal(err)
	}
	hash, err := db.HashPassword("testpass123")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := database.Exec(`INSERT INTO esrv_senders (email, password_hash, domain_id, is_active) VALUES (?, ?, 1, 1)`, "test@example.com", hash); err != nil {
		t.Fatal(err)
	}
	if _, err := database.Exec(`INSERT INTO esrv_whitelisted_ips (ip_address, domain_id, is_active) VALUES ('127.0.0.1', 1, 1)`); err != nil {
		t.Fatal(err)
	}

	cfg := ini.Empty()
	logger := toolbox.GetLogger("test")

	return &Backend{
		DB:                  database,
		DKIM:                dkim.New(database, 1024),
		Relay:               relay.New(database, cfg, logger),
		Cfg:                 cfg,
		Logger:              logger,
		HeloHostname:        "mail.example.com",
		AttachmentsBasePath: t.TempDir(),
	}
}

func startTestServer(t *testing.T, backend *Backend) string {
	t.Helper()
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	srv := NewPlainServer(backend, l.Addr().String(), "mail.example.com")
	go srv.Serve(l)
	t.Cleanup(func() { srv.Close() })
	return l.Addr().String()
}

func TestAuthSuccessAndSenderAuthorization(t *testing.T) {
	backend := newTestBackend(t)
	addr := startTestServer(t, backend)

	c, err := smtp.Dial(addr)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	if err := c.Auth(smtp.PlainAuth("", "test@example.com", "testpass123", "127.0.0.1")); err != nil {
		t.Fatalf("expected auth success, got: %v", err)
	}
	if err := c.Mail("test@example.com"); err != nil {
		t.Fatalf("expected MAIL FROM as own address to succeed, got: %v", err)
	}
	if err := c.Rcpt("someone@elsewhere.example"); err != nil {
		t.Fatalf("expected RCPT to accept any address, got: %v", err)
	}
}

func TestAuthFailureClosesConnection(t *testing.T) {
	backend := newTestBackend(t)
	addr := startTestServer(t, backend)

	c, err := smtp.Dial(addr)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	err = c.Auth(smtp.PlainAuth("", "test@example.com", "wrongpassword", "127.0.0.1"))
	if err == nil {
		t.Fatal("expected auth failure")
	}
	if !strings.Contains(err.Error(), "535") {
		t.Fatalf("expected 535 response, got: %v", err)
	}

	// The server should close the connection shortly after — a subsequent command
	// must fail rather than succeed.
	time.Sleep(300 * time.Millisecond)
	if err := c.Mail("test@example.com"); err == nil {
		t.Fatal("expected connection to have been closed after failed AUTH")
	}
}

func TestIPWhitelistFallbackWithoutAuth(t *testing.T) {
	backend := newTestBackend(t)
	addr := startTestServer(t, backend)

	c, err := smtp.Dial(addr)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	// No AUTH at all: MAIL FROM a domain whitelisted for our (loopback) peer IP.
	if err := c.Mail("anyone@example.com"); err != nil {
		t.Fatalf("expected IP-whitelist fallback to authorize, got: %v", err)
	}
	if err := c.Rcpt("rcpt@elsewhere.example"); err != nil {
		t.Fatalf("expected RCPT to accept, got: %v", err)
	}
}

func TestMailFromRejectedForUnauthorizedDomain(t *testing.T) {
	backend := newTestBackend(t)
	addr := startTestServer(t, backend)

	c, err := smtp.Dial(addr)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	err = c.Mail("nobody@not-whitelisted.example")
	if err == nil {
		t.Fatal("expected MAIL FROM to be rejected for a non-whitelisted, non-authenticated domain")
	}
	if !strings.Contains(err.Error(), "550") {
		t.Fatalf("expected 550 response, got: %v", err)
	}
}

func TestUnverifiedDomainCannotSend(t *testing.T) {
	backend := newTestBackend(t)

	domainID, err := backend.DB.CreateDomain("unverified.example")
	if err != nil {
		t.Fatal(err)
	}
	hash, _ := db.HashPassword("testpass123")
	if _, err := backend.DB.CreateSender("sender@unverified.example", hash, domainID, false, false); err != nil {
		t.Fatal(err)
	}

	addr := startTestServer(t, backend)
	c, err := smtp.Dial(addr)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	if err := c.Auth(smtp.PlainAuth("", "sender@unverified.example", "testpass123", "127.0.0.1")); err != nil {
		t.Fatalf("auth: %v", err)
	}
	err = c.Mail("sender@unverified.example")
	if err == nil {
		t.Fatal("expected MAIL FROM to be rejected for an unverified domain, even for an authenticated sender")
	}
	if !strings.Contains(err.Error(), "550") || !strings.Contains(err.Error(), "verif") {
		t.Fatalf("expected a 550 mentioning verification, got: %v", err)
	}

	// Now verify the domain directly (bypassing DNS) and confirm sending is unblocked.
	if err := backend.DB.SetDomainVerified(domainID, true); err != nil {
		t.Fatal(err)
	}
	if err := c.Mail("sender@unverified.example"); err != nil {
		t.Fatalf("expected MAIL FROM to succeed once domain is verified, got: %v", err)
	}
}

func TestSenderCannotSpoofOtherAddress(t *testing.T) {
	backend := newTestBackend(t)
	addr := startTestServer(t, backend)

	c, err := smtp.Dial(addr)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	if err := c.Auth(smtp.PlainAuth("", "test@example.com", "testpass123", "127.0.0.1")); err != nil {
		t.Fatalf("auth: %v", err)
	}
	err = c.Mail("someoneelse@example.com")
	if err == nil {
		t.Fatal("expected MAIL FROM spoofing another address to be rejected (can_send_as_domain is false)")
	}
	if !strings.Contains(err.Error(), "550") {
		t.Fatalf("expected 550 response, got: %v", err)
	}
}
