package protocol

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

	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/transport"
)

const (
	ResultAccepted = "accepted"
	ResultFailed   = "failed"
	ResultUnknown  = "unknown"
)

const (
	FolderInbox   = "inbox"
	FolderAll     = "all"
	FolderArchive = "archive"
	FolderDrafts  = "drafts"
	FolderFlagged = "flagged"
	FolderJunk    = "junk"
	FolderSent    = "sent"
	FolderTrash   = "trash"
)

const (
	ErrorUnsupported = "unsupported"
	ErrorConflict    = "conflict"
	ErrorNotFound    = "not_found"
	ErrorInvalid     = "invalid"
	ErrorLimit       = "limit"
	ErrorFailed      = "failed"
	ErrorUnknown     = "unknown"
)

type Connector interface {
	ResolveAndDial(
		context.Context,
		transport.Target,
	) (transport.PinnedConnection, error)
}

type Endpoint struct {
	Host     string
	Port     int
	TLSMode  string
	Username string
	Password string
}

type Capabilities struct {
	Values       map[string]bool
	Mailboxes    map[string]string
	DraftMailbox string
}

func (capabilities Capabilities) Has(value string) bool {
	return capabilities.Values[strings.ToUpper(value)]
}

func (capabilities Capabilities) SafeDraftMutation() bool {
	return capabilities.Mailbox(FolderDrafts) != "" &&
		capabilities.Has("SPECIAL-USE") &&
		capabilities.Has("UIDPLUS") &&
		capabilities.Has("CONDSTORE")
}

func (capabilities Capabilities) Mailbox(folder string) string {
	if capabilities.Mailboxes != nil {
		if mailbox := capabilities.Mailboxes[folder]; mailbox != "" {
			return mailbox
		}
	}
	if folder == FolderDrafts {
		return capabilities.DraftMailbox
	}
	return ""
}

type ProtocolError struct {
	Class     string
	Operation string
	Cause     error
}

func (err *ProtocolError) Error() string {
	if err == nil {
		return ""
	}
	if err.Operation == "" {
		return "mail protocol " + err.Class
	}
	return fmt.Sprintf("mail protocol %s: %s", err.Operation, err.Class)
}

func (err *ProtocolError) Unwrap() error {
	if err == nil {
		return nil
	}
	return err.Cause
}

func ErrorClass(err error) string {
	var protocolError *ProtocolError
	if errors.As(err, &protocolError) {
		return protocolError.Class
	}
	return ""
}

type SearchRequest struct {
	Folder         string
	Text           string
	From           []string
	To             []string
	ReceivedAfter  *time.Time
	ReceivedBefore *time.Time
	Unread         *bool
	HasAttachments *bool
	Limit          int
	BeforeUID      uint32
}

type MessageRef struct {
	Mailbox     string
	UIDValidity uint32
	UID         uint32
}

type DraftRef struct {
	Mailbox     string
	UIDValidity uint32
	UID         uint32
	ModSeq      uint64
}

type Body struct {
	Format    string
	Content   string
	Truncated bool
}

type MessageSummary struct {
	Ref            MessageRef
	From           string
	To             []string
	CC             []string
	Subject        string
	SentAt         time.Time
	SizeBytes      uint32
	Unread         bool
	HasAttachments bool
	Truncated      bool
}

type SearchPage struct {
	Messages  []MessageSummary
	BeforeUID uint32
}

type Message struct {
	Ref            MessageRef
	From           string
	To             []string
	CC             []string
	BCC            []string
	Subject        string
	SentAt         time.Time
	Body           Body
	HasAttachments bool
	Truncated      bool
}

type Attachment struct {
	Part      []int
	Filename  string
	MediaType string
	SizeBytes uint32
	Truncated bool
}

type Draft struct {
	Ref         DraftRef
	Content     DraftContent
	Attachments []Attachment
	Truncated   bool
}

// AttachmentContent is the bounded decoded body of one exact MIME part.
type AttachmentContent struct {
	Part      []int
	Filename  string
	MediaType string
	Blob      []byte
}

type IMAP interface {
	Probe(context.Context, Endpoint) (Capabilities, error)
	Search(context.Context, Endpoint, SearchRequest) (SearchPage, error)
	GetMessage(context.Context, Endpoint, MessageRef) (Message, error)
	ListAttachments(context.Context, Endpoint, MessageRef) ([]Attachment, error)
	GetAttachment(
		context.Context,
		Endpoint,
		MessageRef,
		[]int,
	) (AttachmentContent, error)
	AppendDraft(context.Context, Endpoint, DraftContent) (DraftRef, error)
	GetDraft(context.Context, Endpoint, DraftRef) (Draft, error)
	ReplaceDraft(
		context.Context,
		Endpoint,
		DraftRef,
		DraftContent,
	) (DraftRef, error)
}

// SMTP is the one-shot submission seam used by direct send-draft. It performs
// at most one provider attempt per invocation and must never retry or append
// to an IMAP Sent folder.
type SMTP interface {
	Send(context.Context, Endpoint, FrozenMessage) (SendResult, error)
}

// FrozenMessage is the complete message re-read from the provider immediately
// before sending. SMTP derives both the envelope and MIME headers from this
// typed value so callers cannot supply a different sender or raw MIME.
type FrozenMessage struct {
	SenderAddress string
	To            []string
	CC            []string
	BCC           []string
	Subject       string
	BodyFormat    string
	Body          string
	Attachments   []FrozenAttachment
}

// FrozenAttachment is one decoded attachment re-read from the exact draft.
type FrozenAttachment struct {
	Filename  string
	MediaType string
	Blob      []byte
}

type SendResult struct {
	Status string
}

type ClientOptions struct {
	Connector      Connector
	CommandTimeout time.Duration
	// BulkTransferTimeout and DraftBodyTransferTimeout override the bounded
	// bulk deadlines for deterministic tests; zero keeps the defaults.
	BulkTransferTimeout      time.Duration
	DraftBodyTransferTimeout time.Duration
}
