package couch

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"github.com/go-kivik/kivik/v4"
	_ "github.com/go-kivik/kivik/v4/couchdb" // The CouchDB driver
	"mail2couch/config"
	"mail2couch/mail"
)

// Client wraps the Kivik client
type Client struct {
	*kivik.Client
}

// MailDocument represents an email message stored in CouchDB
type MailDocument struct {
	ID        string              `json:"_id,omitempty"`
	Rev       string              `json:"_rev,omitempty"`
	SourceUID string              `json:"sourceUid"` // Unique ID from the mail source (e.g., IMAP UID)
	Mailbox   string              `json:"mailbox"`   // Source mailbox name
	From      []string            `json:"from"`
	To        []string            `json:"to"`
	Subject   string              `json:"subject"`
	Date      time.Time           `json:"date"`
	Body      string              `json:"body"`
	Headers   map[string][]string `json:"headers"`
	StoredAt  time.Time           `json:"storedAt"` // When the document was stored
	DocType   string              `json:"docType"`  // Always "mail"
}

// NewClient creates a new CouchDB client from the configuration
func NewClient(cfg *config.CouchDbConfig) (*Client, error) {
	parsedURL, err := url.Parse(cfg.URL)
	if err != nil {
		return nil, fmt.Errorf("invalid couchdb url: %w", err)
	}

	parsedURL.User = url.UserPassword(cfg.User, cfg.Password)
	dsn := parsedURL.String()

	client, err := kivik.New("couch", dsn)
	if err != nil {
		return nil, err
	}

	return &Client{client}, nil
}

// EnsureDB ensures that the configured database exists.
func (c *Client) EnsureDB(ctx context.Context, dbName string) error {
	exists, err := c.DBExists(ctx, dbName)
	if err != nil {
		return err
	}
	if !exists {
		return c.CreateDB(ctx, dbName)
	}
	return nil
}

// ConvertMessage converts an IMAP message to a MailDocument
func ConvertMessage(msg *mail.Message, mailbox string) *MailDocument {
	docID := fmt.Sprintf("%s_%d", mailbox, msg.UID)

	return &MailDocument{
		ID:        docID,
		SourceUID: fmt.Sprintf("%d", msg.UID),
		Mailbox:   mailbox,
		From:      msg.From,
		To:        msg.To,
		Subject:   msg.Subject,
		Date:      msg.Date,
		Body:      msg.Body,
		Headers:   msg.Headers,
		StoredAt:  time.Now(),
		DocType:   "mail",
	}
}

// StoreMessage stores a mail message in CouchDB
func (c *Client) StoreMessage(ctx context.Context, dbName string, doc *MailDocument) error {
	db := c.DB(dbName)
	if db.Err() != nil {
		return db.Err()
	}

	// Check if document already exists
	exists, err := c.DocumentExists(ctx, dbName, doc.ID)
	if err != nil {
		return fmt.Errorf("failed to check if document exists: %w", err)
	}

	if exists {
		return nil // Document already exists, skip
	}

	// Store the document
	_, err = db.Put(ctx, doc.ID, doc)
	if err != nil {
		return fmt.Errorf("failed to store document: %w", err)
	}

	return nil
}

// StoreMessages stores multiple mail messages in CouchDB
func (c *Client) StoreMessages(ctx context.Context, dbName string, docs []*MailDocument) error {
	for _, doc := range docs {
		if err := c.StoreMessage(ctx, dbName, doc); err != nil {
			return err
		}
	}
	return nil
}

// DocumentExists checks if a document with the given ID already exists.
func (c *Client) DocumentExists(ctx context.Context, dbName, docID string) (bool, error) {
	db := c.DB(dbName)
	if db.Err() != nil {
		return false, db.Err()
	}

	row := db.Get(ctx, docID)
	return row.Err() == nil, nil
}
