package store

import (
	"errors"
	"fmt"
	"time"

	"github.com/google/uuid"
)

type Mailbox struct {
	ID            string     `json:"id"`
	WorkspaceSlug string     `json:"workspaceSlug"`
	Label         string     `json:"label"`
	Email         string     `json:"email"`
	DisplayName   string     `json:"displayName"`
	IMAPHost      string     `json:"imapHost"`
	IMAPPort      int        `json:"imapPort"`
	IMAPUsername  string     `json:"imapUsername"`
	IMAPUseTLS    bool       `json:"imapUseTls"`
	SMTPHost      string     `json:"smtpHost"`
	SMTPPort      int        `json:"smtpPort"`
	SMTPUsername  string     `json:"smtpUsername"`
	SMTPUseTLS    bool       `json:"smtpUseTls"`
	CreatedAt     time.Time  `json:"createdAt"`
	UpdatedAt     time.Time  `json:"updatedAt"`
	LastSyncedAt  *time.Time `json:"lastSyncedAt,omitempty"`
	SyncStatus    string     `json:"syncStatus"`
	SyncError     string     `json:"syncError,omitempty"`
}

type MailAddress struct {
	Name  string `json:"name"`
	Email string `json:"email"`
}

type MailMessage struct {
	ID            string        `json:"id"`
	WorkspaceSlug string        `json:"workspaceSlug"`
	MailboxID     string        `json:"mailboxId"`
	RemoteUID     int64         `json:"remoteUid"`
	MessageID     string        `json:"messageId"`
	Folder        string        `json:"folder"`
	From          MailAddress   `json:"from"`
	To            []MailAddress `json:"to"`
	Cc            []MailAddress `json:"cc"`
	Subject       string        `json:"subject"`
	Snippet       string        `json:"snippet"`
	TextBody      string        `json:"textBody"`
	HTMLBody      string        `json:"htmlBody"`
	ReceivedAt    time.Time     `json:"receivedAt"`
	IsRead        bool          `json:"isRead"`
	LinkedTaskID  *string       `json:"linkedTaskId,omitempty"`
	CreatedAt     time.Time     `json:"createdAt"`
	UpdatedAt     time.Time     `json:"updatedAt"`
}

type OutgoingMail struct {
	ID            string        `json:"id"`
	WorkspaceSlug string        `json:"workspaceSlug"`
	MailboxID     string        `json:"mailboxId"`
	To            []MailAddress `json:"to"`
	Cc            []MailAddress `json:"cc"`
	Bcc           []MailAddress `json:"bcc"`
	Subject       string        `json:"subject"`
	TextBody      string        `json:"textBody"`
	HTMLBody      string        `json:"htmlBody"`
	Status        string        `json:"status"`
	ScheduledFor  *time.Time    `json:"scheduledFor,omitempty"`
	SentAt        *time.Time    `json:"sentAt,omitempty"`
	Error         string        `json:"error,omitempty"`
	CreatedAt     time.Time     `json:"createdAt"`
	UpdatedAt     time.Time     `json:"updatedAt"`
}

type MailboxConnection struct {
	Mailbox
	IMAPPasswordCiphertext string
	SMTPPasswordCiphertext string
}

type CreateMailboxRecordInput struct {
	WorkspaceSlug          string
	Label                  string
	Email                  string
	DisplayName            string
	IMAPHost               string
	IMAPPort               int
	IMAPUsername           string
	IMAPPasswordCiphertext string
	IMAPUseTLS             bool
	SMTPHost               string
	SMTPPort               int
	SMTPUsername           string
	SMTPPasswordCiphertext string
	SMTPUseTLS             bool
}

type UpdateMailboxSyncStatusInput struct {
	SyncStatus   string
	SyncError    *string
	LastSyncedAt *time.Time
}

type InboundMailMessage struct {
	WorkspaceSlug string
	MailboxID     string
	RemoteUID     int64
	MessageID     string
	Folder        string
	From          MailAddress
	To            []MailAddress
	Cc            []MailAddress
	Subject       string
	Snippet       string
	TextBody      string
	HTMLBody      string
	ReceivedAt    time.Time
	IsRead        bool
}

type CreateOutgoingMailInput struct {
	WorkspaceSlug string
	MailboxID     string
	To            []MailAddress
	Cc            []MailAddress
	Bcc           []MailAddress
	Subject       string
	TextBody      string
	HTMLBody      string
	Status        string
	ScheduledFor  *time.Time
}

type UpdateOutgoingMailStatusInput struct {
	Status string
	SentAt *time.Time
	Error  *string
}

func (s *State) ListAllMailboxes() []Mailbox {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return append([]Mailbox(nil), s.Mailboxes...)
}

func (s *State) ListMailboxes(workspaceSlug string) []Mailbox {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return filterByWorkspace(s.Mailboxes, workspaceSlug)
}

func (s *State) GetMailboxByID(mailboxID string) (Mailbox, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, mailbox := range s.Mailboxes {
		if mailbox.ID == mailboxID {
			return mailbox, nil
		}
	}

	return Mailbox{}, errors.New("mailbox not found")
}

func (s *State) GetMailboxConnection(mailboxID string) (MailboxConnection, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	connection, ok := s.MailboxAuth[mailboxID]
	if !ok {
		return MailboxConnection{}, errors.New("mailbox connection not found")
	}

	return connection, nil
}

func (s *State) CreateMailbox(input CreateMailboxRecordInput) (Mailbox, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	now := time.Now().UTC()
	mailbox := Mailbox{
		ID:            uuid.NewString(),
		WorkspaceSlug: input.WorkspaceSlug,
		Label:         input.Label,
		Email:         input.Email,
		DisplayName:   input.DisplayName,
		IMAPHost:      input.IMAPHost,
		IMAPPort:      input.IMAPPort,
		IMAPUsername:  input.IMAPUsername,
		IMAPUseTLS:    input.IMAPUseTLS,
		SMTPHost:      input.SMTPHost,
		SMTPPort:      input.SMTPPort,
		SMTPUsername:  input.SMTPUsername,
		SMTPUseTLS:    input.SMTPUseTLS,
		CreatedAt:     now,
		UpdatedAt:     now,
		SyncStatus:    "idle",
	}
	s.Mailboxes = append([]Mailbox{mailbox}, s.Mailboxes...)
	s.MailboxAuth[mailbox.ID] = MailboxConnection{
		Mailbox:                mailbox,
		IMAPPasswordCiphertext: input.IMAPPasswordCiphertext,
		SMTPPasswordCiphertext: input.SMTPPasswordCiphertext,
	}
	s.appendActivityLocked(input.WorkspaceSlug, "Mailbox connected", fmt.Sprintf("%s is ready for sync.", input.Email))
	return mailbox, nil
}

func (s *State) UpdateMailboxSyncStatus(mailboxID string, input UpdateMailboxSyncStatusInput) (Mailbox, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, mailbox := range s.Mailboxes {
		if mailbox.ID != mailboxID {
			continue
		}

		if input.SyncStatus != "" {
			mailbox.SyncStatus = input.SyncStatus
		}
		if input.SyncError != nil {
			mailbox.SyncError = *input.SyncError
		}
		if input.LastSyncedAt != nil {
			mailbox.LastSyncedAt = input.LastSyncedAt
		}
		mailbox.UpdatedAt = time.Now().UTC()
		s.Mailboxes[index] = mailbox
		if connection, ok := s.MailboxAuth[mailboxID]; ok {
			connection.Mailbox = mailbox
			s.MailboxAuth[mailboxID] = connection
		}
		return mailbox, nil
	}

	return Mailbox{}, errors.New("mailbox not found")
}

func (s *State) ListMailMessages(workspaceSlug string, mailboxID string) []MailMessage {
	s.mu.RLock()
	defer s.mu.RUnlock()

	items := filterByWorkspace(s.MailMessages, workspaceSlug)
	if mailboxID == "" {
		return items
	}

	filtered := make([]MailMessage, 0, len(items))
	for _, item := range items {
		if item.MailboxID == mailboxID {
			filtered = append(filtered, item)
		}
	}
	return filtered
}

func (s *State) GetMailMessageByID(messageID string) (MailMessage, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, message := range s.MailMessages {
		if message.ID == messageID {
			return message, nil
		}
	}

	return MailMessage{}, errors.New("mail message not found")
}

func (s *State) UpsertMailMessages(mailboxID string, messages []InboundMailMessage) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	now := time.Now().UTC()
	for _, input := range messages {
		found := false
		for index, message := range s.MailMessages {
			if message.MailboxID == mailboxID && message.Folder == input.Folder && message.RemoteUID == input.RemoteUID {
				linkedTaskID := message.LinkedTaskID
				s.MailMessages[index] = MailMessage{
					ID:            message.ID,
					WorkspaceSlug: input.WorkspaceSlug,
					MailboxID:     mailboxID,
					RemoteUID:     input.RemoteUID,
					MessageID:     input.MessageID,
					Folder:        input.Folder,
					From:          input.From,
					To:            input.To,
					Cc:            input.Cc,
					Subject:       input.Subject,
					Snippet:       input.Snippet,
					TextBody:      input.TextBody,
					HTMLBody:      input.HTMLBody,
					ReceivedAt:    input.ReceivedAt,
					IsRead:        input.IsRead,
					LinkedTaskID:  linkedTaskID,
					CreatedAt:     message.CreatedAt,
					UpdatedAt:     now,
				}
				found = true
				break
			}
		}
		if found {
			continue
		}
		s.MailMessages = append([]MailMessage{{
			ID:            uuid.NewString(),
			WorkspaceSlug: input.WorkspaceSlug,
			MailboxID:     mailboxID,
			RemoteUID:     input.RemoteUID,
			MessageID:     input.MessageID,
			Folder:        input.Folder,
			From:          input.From,
			To:            input.To,
			Cc:            input.Cc,
			Subject:       input.Subject,
			Snippet:       input.Snippet,
			TextBody:      input.TextBody,
			HTMLBody:      input.HTMLBody,
			ReceivedAt:    input.ReceivedAt,
			IsRead:        input.IsRead,
			CreatedAt:     now,
			UpdatedAt:     now,
		}}, s.MailMessages...)
	}

	return nil
}

func (s *State) LinkMailMessageTask(messageID string, taskID string) (MailMessage, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, message := range s.MailMessages {
		if message.ID != messageID {
			continue
		}
		message.LinkedTaskID = &taskID
		message.UpdatedAt = time.Now().UTC()
		s.MailMessages[index] = message
		return message, nil
	}

	return MailMessage{}, errors.New("mail message not found")
}

func (s *State) ListOutgoingMails(workspaceSlug string, mailboxID string) []OutgoingMail {
	s.mu.RLock()
	defer s.mu.RUnlock()

	items := filterByWorkspace(s.OutgoingMails, workspaceSlug)
	if mailboxID == "" {
		return items
	}

	filtered := make([]OutgoingMail, 0, len(items))
	for _, item := range items {
		if item.MailboxID == mailboxID {
			filtered = append(filtered, item)
		}
	}
	return filtered
}

func (s *State) ListDueOutgoingMails(now time.Time, limit int) []OutgoingMail {
	s.mu.RLock()
	defer s.mu.RUnlock()

	items := make([]OutgoingMail, 0, limit)
	for _, item := range s.OutgoingMails {
		if item.Status != "queued" && item.Status != "scheduled" {
			continue
		}
		if item.ScheduledFor != nil && item.ScheduledFor.After(now) {
			continue
		}
		items = append(items, item)
		if limit > 0 && len(items) >= limit {
			break
		}
	}
	return items
}

func (s *State) GetOutgoingMailByID(outgoingMailID string) (OutgoingMail, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, item := range s.OutgoingMails {
		if item.ID == outgoingMailID {
			return item, nil
		}
	}

	return OutgoingMail{}, errors.New("outgoing mail not found")
}

func (s *State) CreateOutgoingMail(input CreateOutgoingMailInput) (OutgoingMail, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	now := time.Now().UTC()
	item := OutgoingMail{
		ID:            uuid.NewString(),
		WorkspaceSlug: input.WorkspaceSlug,
		MailboxID:     input.MailboxID,
		To:            input.To,
		Cc:            input.Cc,
		Bcc:           input.Bcc,
		Subject:       input.Subject,
		TextBody:      input.TextBody,
		HTMLBody:      input.HTMLBody,
		Status:        input.Status,
		ScheduledFor:  input.ScheduledFor,
		CreatedAt:     now,
		UpdatedAt:     now,
	}
	s.OutgoingMails = append([]OutgoingMail{item}, s.OutgoingMails...)
	s.appendActivityLocked(input.WorkspaceSlug, "Outgoing mail queued", input.Subject)
	return item, nil
}

func (s *State) UpdateOutgoingMailStatus(outgoingMailID string, input UpdateOutgoingMailStatusInput) (OutgoingMail, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, item := range s.OutgoingMails {
		if item.ID != outgoingMailID {
			continue
		}
		if input.Status != "" {
			item.Status = input.Status
		}
		if input.SentAt != nil {
			item.SentAt = input.SentAt
		}
		if input.Error != nil {
			item.Error = *input.Error
		}
		item.UpdatedAt = time.Now().UTC()
		s.OutgoingMails[index] = item
		return item, nil
	}

	return OutgoingMail{}, errors.New("outgoing mail not found")
}

func (item Mailbox) GetWorkspaceSlug() string      { return item.WorkspaceSlug }
func (item MailMessage) GetWorkspaceSlug() string  { return item.WorkspaceSlug }
func (item OutgoingMail) GetWorkspaceSlug() string { return item.WorkspaceSlug }
