package connect

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/emersion/go-imap/v2/imapclient"
	"github.com/emersion/go-sasl"
	gosmtp "github.com/emersion/go-smtp"

	"github.com/ultisuite/ulti-backend/internal/mail/credentials"
)

type ServerConfig struct {
	IMAPHost string
	IMAPPort int
	IMAPTLS  bool
	SMTPHost string
	SMTPPort int
	SMTPTLS  bool
}

type TestResult struct {
	OK        bool   `json:"ok"`
	IMAP      bool   `json:"imap_ok"`
	IMAPError string `json:"imap_error,omitempty"`
	SMTP      bool   `json:"smtp_ok"`
	SMTPError string `json:"smtp_error,omitempty"`
}

func Test(ctx context.Context, cfg ServerConfig, cred credentials.Credential) TestResult {
	result := TestResult{}
	if cfg.IMAPHost != "" {
		if err := testIMAP(ctx, cfg, cred); err != nil {
			result.IMAPError = err.Error()
		} else {
			result.IMAP = true
		}
	}
	if cfg.SMTPHost != "" {
		if err := testSMTP(ctx, cfg, cred); err != nil {
			result.SMTPError = err.Error()
		} else {
			result.SMTP = true
		}
	}
	result.OK = (cfg.IMAPHost == "" || result.IMAP) && (cfg.SMTPHost == "" || result.SMTP)
	return result
}

func testIMAP(ctx context.Context, cfg ServerConfig, cred credentials.Credential) error {
	ctx, cancel := context.WithTimeout(ctx, 12*time.Second)
	defer cancel()

	addr := fmt.Sprintf("%s:%d", cfg.IMAPHost, cfg.IMAPPort)
	var client *imapclient.Client
	var err error
	if cfg.IMAPTLS {
		client, err = imapclient.DialTLS(addr, &imapclient.Options{})
	} else {
		client, err = imapclient.DialStartTLS(addr, &imapclient.Options{})
	}
	if err != nil {
		return fmt.Errorf("connexion IMAP: %w", err)
	}
	defer client.Close()

	if err := authenticateIMAP(client, cred); err != nil {
		return fmt.Errorf("authentification IMAP: %w", err)
	}
	if err := client.Noop().Wait(); err != nil {
		return fmt.Errorf("IMAP NOOP: %w", err)
	}
	return nil
}

func authenticateIMAP(client *imapclient.Client, cred credentials.Credential) error {
	if cred.IsOAuth() {
		saslClient := sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{
			Username: cred.Username,
			Token:    cred.AccessToken,
		})
		if err := client.Authenticate(saslClient); err != nil {
			return err
		}
		return nil
	}
	return client.Login(cred.Username, cred.Password).Wait()
}

func testSMTP(ctx context.Context, cfg ServerConfig, cred credentials.Credential) error {
	ctx, cancel := context.WithTimeout(ctx, 12*time.Second)
	defer cancel()

	addr := fmt.Sprintf("%s:%d", cfg.SMTPHost, cfg.SMTPPort)
	auth, err := smtpAuth(cred)
	if err != nil {
		return err
	}

	done := make(chan error, 1)
	go func() {
		var c *gosmtp.Client
		var dialErr error
		if cfg.SMTPTLS {
			c, dialErr = gosmtp.DialTLS(addr, nil)
		} else {
			c, dialErr = gosmtp.Dial(addr)
		}
		if dialErr != nil {
			done <- dialErr
			return
		}
		defer c.Close()
		if auth != nil {
			if err := c.Auth(auth); err != nil {
				done <- err
				return
			}
		}
		done <- c.Noop()
	}()

	select {
	case <-ctx.Done():
		return fmt.Errorf("connexion SMTP: délai dépassé")
	case err := <-done:
		if err != nil {
			return fmt.Errorf("connexion SMTP: %w", err)
		}
		return nil
	}
}

func smtpAuth(cred credentials.Credential) (sasl.Client, error) {
	if cred.IsOAuth() {
		return sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{
			Username: cred.Username,
			Token:    cred.AccessToken,
		}), nil
	}
	if cred.Username == "" || cred.Password == "" {
		return nil, fmt.Errorf("identifiants incomplets")
	}
	return sasl.NewPlainClient("", cred.Username, cred.Password), nil
}

// SanitizeError shortens provider errors for API responses.
func SanitizeError(msg string) string {
	msg = strings.TrimSpace(msg)
	if len(msg) > 240 {
		return msg[:240] + "…"
	}
	return msg
}
