package mail

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/ultisuite/ulti-backend/internal/api/query"
	"github.com/ultisuite/ulti-backend/internal/mail/imap"
)

type MessageSearchFilter struct {
	Query            string
	Sender           string
	DateFrom         *time.Time
	DateTo           *time.Time
	HasAttachments   *bool
	Label            string
	AccountID        string
	ScopedAccountIDs []string
}

type MessageSearchResult struct {
	Messages   []map[string]any     `json:"messages"`
	Pagination query.PaginationMeta `json:"pagination,omitempty"`
}

func (s *Service) SearchMessages(ctx context.Context, externalID string, filter MessageSearchFilter, params query.ListParams) (MessageSearchResult, error) {
	base := `
		FROM messages m
		JOIN mail_accounts ma ON m.account_id = ma.id
		WHERE ma.user_id = (SELECT id FROM users WHERE external_id = $1)
	`
	args := []any{externalID}
	argIdx := 2

	base, args, argIdx = appendMessageAccountScope(base, args, argIdx, filter.AccountID, filter.ScopedAccountIDs)
	if filter.Sender != "" {
		base += fmt.Sprintf(" AND m.from_addr::text ILIKE '%%' || $%d || '%%'", argIdx)
		args = append(args, filter.Sender)
		argIdx++
	}
	if filter.DateFrom != nil {
		base += fmt.Sprintf(" AND m.date >= $%d", argIdx)
		args = append(args, *filter.DateFrom)
		argIdx++
	}
	if filter.DateTo != nil {
		base += fmt.Sprintf(" AND m.date <= $%d", argIdx)
		args = append(args, *filter.DateTo)
		argIdx++
	}
	if filter.HasAttachments != nil {
		base += fmt.Sprintf(" AND m.has_attachments = $%d", argIdx)
		args = append(args, *filter.HasAttachments)
		argIdx++
	}
	if filter.Label != "" {
		base += fmt.Sprintf(" AND $%d = ANY(m.labels)", argIdx)
		args = append(args, filter.Label)
		argIdx++
	}
	if q := strings.TrimSpace(filter.Query); q != "" {
		tsQuery := toMailTSQuery(q)
		base += fmt.Sprintf(" AND m.search_vector @@ to_tsquery('simple', $%d)", argIdx)
		args = append(args, tsQuery)
		argIdx++
	}

	var total int64
	if err := s.db.QueryRow(ctx, "SELECT COUNT(*) "+base, args...).Scan(&total); err != nil {
		return MessageSearchResult{}, err
	}

	listQuery := `
		SELECT m.id, m.message_id, m.thread_id, m.subject, m.from_addr, m.to_addrs,
		       m.date, m.snippet, m.flags, m.labels, m.has_attachments
	` + base + fmt.Sprintf(" ORDER BY m.date DESC LIMIT $%d OFFSET $%d", argIdx, argIdx+1)
	args = append(args, params.Limit(), params.Offset())

	rows, err := s.db.Query(ctx, listQuery, args...)
	if err != nil {
		return MessageSearchResult{}, err
	}
	defer rows.Close()

	messages := make([]map[string]any, 0)
	for rows.Next() {
		var id, messageID, subject, snippet string
		var threadID *string
		var fromAddr, toAddrs []byte
		var date any
		var flags, labels []string
		var hasAttachments bool
		if err := rows.Scan(&id, &messageID, &threadID, &subject, &fromAddr, &toAddrs, &date, &snippet, &flags, &labels, &hasAttachments); err != nil {
			return MessageSearchResult{}, err
		}
		entry := map[string]any{
			"id": id, "message_id": messageID, "subject": subject,
			"from": json.RawMessage(fromAddr), "to": json.RawMessage(toAddrs),
			"date": date, "snippet": imap.RepairSnippet(snippet), "flags": flags, "labels": labels,
			"has_attachments": hasAttachments,
		}
		if threadID != nil {
			entry["thread_id"] = *threadID
		}
		messages = append(messages, entry)
	}
	if err := rows.Err(); err != nil {
		return MessageSearchResult{}, err
	}

	return MessageSearchResult{
		Messages:   messages,
		Pagination: params.Meta(&total),
	}, nil
}

func toMailTSQuery(input string) string {
	words := strings.Fields(input)
	for i, w := range words {
		words[i] = w + ":*"
	}
	return strings.Join(words, " & ")
}
