package internal


import (
	"database/sql"
	"fmt"
	"log/slog"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

var db *sql.DB

// userDBs stores per-user database connections (keyed by user ID)
var userDBs = make(map[string]*sql.DB)
var userDBsMutex sync.RWMutex

// truncateString truncates a string to maxLen characters for logging
func truncateString(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen] + "..."
}

// SanitizeUsername converts a username to a safe filesystem name
func SanitizeUsername(username string) string {
	// Convert to lowercase
	safe := strings.ToLower(username)

	// Replace spaces and special characters with underscores
	reg := regexp.MustCompile(`[^a-z0-9]+`)
	safe = reg.ReplaceAllString(safe, "_")

	// Remove leading/trailing underscores
	safe = strings.Trim(safe, "_")

	// Ensure it's not empty
	if safe == "" {
		safe = "user"
	}

	return safe
}

func InitDB(filepath string) error {
	var err error
	db, err = sql.Open("sqlite3", filepath)
	if err != nil {
		return err
	}

	if err = db.Ping(); err != nil {
		return err
	}

	createTableSQL := `
	-- Unified table for SMS messages, MMS messages, and call logs
	-- record_type: 1 = SMS, 2 = MMS, 3 = call
	CREATE TABLE IF NOT EXISTS messages (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		record_type INTEGER NOT NULL DEFAULT 1,
		address TEXT NOT NULL,
		body TEXT,
		type INTEGER NOT NULL,
		date INTEGER NOT NULL,
		read INTEGER DEFAULT 0,
		thread_id INTEGER,
		subject TEXT,
		media_type TEXT,
		media_data BLOB,
		protocol INTEGER,
		status INTEGER,
		service_center TEXT,
		sub_id INTEGER,
		contact_name TEXT,
		sender TEXT,
		content_type TEXT,
		read_report INTEGER,
		read_status INTEGER,
		message_id TEXT,
		message_size INTEGER,
		message_type INTEGER,
		sim_slot INTEGER,
		addresses TEXT,
		duration INTEGER,
		presentation INTEGER,
		subscription_id TEXT
	);

	CREATE INDEX IF NOT EXISTS idx_address ON messages(address);
	CREATE INDEX IF NOT EXISTS idx_date ON messages(date);
	CREATE INDEX IF NOT EXISTS idx_thread ON messages(thread_id);
	CREATE INDEX IF NOT EXISTS idx_record_type ON messages(record_type);
	CREATE INDEX IF NOT EXISTS idx_record_type_date ON messages(record_type, date);

	-- Create unique constraints for idempotent imports
	-- record_type differentiates SMS (1), MMS (2), and calls (3)
	CREATE UNIQUE INDEX IF NOT EXISTS idx_message_unique ON messages(record_type, address, date, type, COALESCE(body, ''), COALESCE(content_type, ''), COALESCE(message_id, ''), COALESCE(duration, 0));

	-- Create FTS5 virtual table for full-text search of messages
	CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
		message_id UNINDEXED,
		address UNINDEXED,
		body,
		contact_name UNINDEXED,
		date UNINDEXED,
		content='messages',
		content_rowid='id'
	);

	-- Create triggers to keep FTS table in sync
	CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
		INSERT INTO messages_fts(rowid, message_id, address, body, contact_name, date)
		VALUES (new.id, new.id, new.address, new.body, new.contact_name, new.date);
	END;

	CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
		INSERT INTO messages_fts(messages_fts, rowid, message_id, address, body, contact_name, date)
		VALUES('delete', old.id, old.id, old.address, old.body, old.contact_name, old.date);
	END;

	CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
		INSERT INTO messages_fts(messages_fts, rowid, message_id, address, body, contact_name, date)
		VALUES('delete', old.id, old.id, old.address, old.body, old.contact_name, old.date);
		INSERT INTO messages_fts(rowid, message_id, address, body, contact_name, date)
		VALUES (new.id, new.id, new.address, new.body, new.contact_name, new.date);
	END;
	`

	_, err = db.Exec(createTableSQL)
	if err != nil {
		return err
	}

	slog.Info("Database initialized successfully")
	return nil
}

// InitUserDB initializes a database for a specific user
func InitUserDB(userID string, filepath string) error {
	userDB, err := sql.Open("sqlite3", filepath)
	if err != nil {
		return err
	}

	if err = userDB.Ping(); err != nil {
		return err
	}

	createTableSQL := `
	-- Unified table for SMS messages, MMS messages, and call logs
	-- record_type: 1 = SMS, 2 = MMS, 3 = call
	CREATE TABLE IF NOT EXISTS messages (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		record_type INTEGER NOT NULL DEFAULT 1,
		address TEXT NOT NULL,
		body TEXT,
		type INTEGER NOT NULL,
		date INTEGER NOT NULL,
		read INTEGER DEFAULT 0,
		thread_id INTEGER,
		subject TEXT,
		media_type TEXT,
		media_data BLOB,
		protocol INTEGER,
		status INTEGER,
		service_center TEXT,
		sub_id INTEGER,
		contact_name TEXT,
		sender TEXT,
		content_type TEXT,
		read_report INTEGER,
		read_status INTEGER,
		message_id TEXT,
		message_size INTEGER,
		message_type INTEGER,
		sim_slot INTEGER,
		addresses TEXT,
		duration INTEGER,
		presentation INTEGER,
		subscription_id TEXT
	);

	CREATE INDEX IF NOT EXISTS idx_address ON messages(address);
	CREATE INDEX IF NOT EXISTS idx_date ON messages(date);
	CREATE INDEX IF NOT EXISTS idx_thread ON messages(thread_id);
	CREATE INDEX IF NOT EXISTS idx_record_type ON messages(record_type);
	CREATE INDEX IF NOT EXISTS idx_record_type_date ON messages(record_type, date);

	-- record_type differentiates SMS (1), MMS (2), and calls (3)
	CREATE UNIQUE INDEX IF NOT EXISTS idx_message_unique ON messages(record_type, address, date, type, COALESCE(body, ''), COALESCE(content_type, ''), COALESCE(message_id, ''), COALESCE(duration, 0));

	CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
		message_id UNINDEXED,
		address UNINDEXED,
		body,
		contact_name UNINDEXED,
		date UNINDEXED,
		content='messages',
		content_rowid='id'
	);

	CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
		INSERT INTO messages_fts(rowid, message_id, address, body, contact_name, date)
		VALUES (new.id, new.id, new.address, new.body, new.contact_name, new.date);
	END;

	CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
		INSERT INTO messages_fts(messages_fts, rowid, message_id, address, body, contact_name, date)
		VALUES('delete', old.id, old.id, old.address, old.body, old.contact_name, old.date);
	END;

	CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
		INSERT INTO messages_fts(messages_fts, rowid, message_id, address, body, contact_name, date)
		VALUES('delete', old.id, old.id, old.address, old.body, old.contact_name, old.date);
		INSERT INTO messages_fts(rowid, message_id, address, body, contact_name, date)
		VALUES (new.id, new.id, new.address, new.body, new.contact_name, new.date);
	END;
	`

	_, err = userDB.Exec(createTableSQL)
	if err != nil {
		return err
	}

	// Store in map
	userDBsMutex.Lock()
	userDBs[userID] = userDB
	userDBsMutex.Unlock()

	slog.Info("User database initialized", "user_id", userID, "path", filepath)
	return nil
}

// GetUserDB retrieves the database connection for a specific user
func GetUserDB(userID string, username string) (*sql.DB, error) {
	userDBsMutex.RLock()
	defer userDBsMutex.RUnlock()

	userDB, exists := userDBs[userID]
	if !exists {
		// Try to open the database if it exists
		dbPathPrefix := os.Getenv("DB_PATH_PREFIX")
		if dbPathPrefix == "" {
			dbPathPrefix = "."
		}
		// Use UUID as database filename instead of sanitized username
		filepath := fmt.Sprintf("%s/sbv_%s.db", dbPathPrefix, userID)
		if _, err := os.Stat(filepath); err == nil {
			// Database file exists, try to open it
			userDBsMutex.RUnlock()
			if err := InitUserDB(userID, filepath); err != nil {
				userDBsMutex.RLock()
				return nil, fmt.Errorf("failed to open user database: %w", err)
			}
			userDBsMutex.RLock()
			userDB = userDBs[userID]
		} else {
			return nil, fmt.Errorf("user database not found for user %s", username)
		}
	}

	return userDB, nil
}

func InsertMessage(userDB *sql.DB, msg *Message) error {
	// Convert addresses slice to JSON string
	var addressesJSON string
	if len(msg.Addresses) > 0 {
		addresses := strings.Join(msg.Addresses, ",")
		addressesJSON = addresses
	}

	// Determine record type: 1 = SMS, 2 = MMS
	// MMS messages have ContentType set (e.g., 'application/vnd.wap.multipart.related')
	// SMS messages do not have ContentType
	recordType := 1 // Default to SMS
	if msg.ContentType != "" {
		recordType = 2 // MMS
	}

	query := `
		INSERT INTO messages (
			record_type, address, body, type, date, read, thread_id, subject, media_type, media_data,
			protocol, status, service_center, sub_id, contact_name, sender,
			content_type, read_report, read_status, message_id, message_size, message_type, sim_slot, addresses
		)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
		ON CONFLICT DO NOTHING
	`
	result, err := userDB.Exec(query,
		recordType, // record_type: 1 = SMS, 2 = MMS
		msg.Address,
		msg.Body,
		msg.Type,
		msg.Date.Unix(),
		msg.Read,
		msg.ThreadID,
		msg.Subject,
		msg.MediaType,
		msg.MediaData,
		msg.Protocol,
		msg.Status,
		msg.ServiceCenter,
		msg.SubID,
		msg.ContactName,
		msg.Sender,
		msg.ContentType,
		msg.ReadReport,
		msg.ReadStatus,
		msg.MessageID,
		msg.MessageSize,
		msg.MessageType,
		msg.SimSlot,
		addressesJSON,
	)
	if err != nil {
		slog.Debug("InsertMessage: Error inserting message", "error", err)
		return err
	}

	id, err := result.LastInsertId()
	if err != nil {
		return err
	}
	msg.ID = id

	return nil
}

func InsertCallLog(userDB *sql.DB, call *CallLog) error {
	query := `
		INSERT INTO messages (record_type, address, type, date, duration, presentation, subscription_id, contact_name)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?)
		ON CONFLICT DO NOTHING
	`
	result, err := userDB.Exec(query,
		3, // record_type: 3 = call
		call.Number,
		call.Type,
		call.Date.Unix(),
		call.Duration,
		call.Presentation,
		call.SubscriptionID,
		call.ContactName,
	)
	if err != nil {
		return err
	}

	id, err := result.LastInsertId()
	if err != nil {
		return err
	}
	call.ID = id
	return nil
}

// InsertCallLogBatch inserts multiple call logs in a single transaction for better performance
func InsertCallLogBatch(userDB *sql.DB, calls []CallLog) error {
	if len(calls) == 0 {
		return nil
	}

	tx, err := userDB.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	stmt, err := tx.Prepare(`
		INSERT INTO messages (record_type, address, type, date, duration, presentation, subscription_id, contact_name)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?)
		ON CONFLICT DO NOTHING
	`)
	if err != nil {
		return err
	}
	defer stmt.Close()

	for i := range calls {
		_, err := stmt.Exec(
			3, // record_type: 3 = call
			calls[i].Number,
			calls[i].Type,
			calls[i].Date.Unix(),
			calls[i].Duration,
			calls[i].Presentation,
			calls[i].SubscriptionID,
			calls[i].ContactName,
		)
		if err != nil {
			return err
		}
	}

	return tx.Commit()
}

func GetConversations(userDB *sql.DB, startDate, endDate *time.Time) ([]Conversation, error) {
	// Build a query that groups all activity (messages and calls) by address
	query := `
		SELECT
			address,
			MAX(COALESCE(contact_name, '')) as contact_name,
			(
				SELECT COALESCE(subject, '')
				FROM messages m2
				WHERE m2.address = messages.address
					AND m2.subject IS NOT NULL
					AND m2.subject != ''
				ORDER BY date DESC
				LIMIT 1
			) as subject,
			(
				SELECT
					CASE
						WHEN record_type = 1 THEN body  -- SMS
						WHEN record_type = 2 THEN body  -- MMS
						WHEN record_type = 3 AND type = 1 THEN 'Incoming call'
						WHEN record_type = 3 AND type = 2 THEN 'Outgoing call'
						WHEN record_type = 3 AND type = 3 THEN 'Missed call'
						WHEN record_type = 3 AND type = 4 THEN 'Voicemail'
						WHEN record_type = 3 AND type = 5 THEN 'Rejected call'
						WHEN record_type = 3 AND type = 6 THEN 'Refused call'
						ELSE 'Call'
					END
				FROM messages m3
				WHERE m3.address = messages.address
				ORDER BY date DESC
				LIMIT 1
			) as last_message,
			MAX(date) as last_date,
			COUNT(*) as activity_count
		FROM messages
		WHERE 1=1
	`

	args := []interface{}{}
	if startDate != nil {
		query += " AND date >= ?"
		args = append(args, startDate.Unix())
	}
	if endDate != nil {
		query += " AND date <= ?"
		args = append(args, endDate.Unix())
	}

	query += `
		GROUP BY address
		ORDER BY last_date DESC
	`

	rows, err := userDB.Query(query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	conversations := []Conversation{}
	for rows.Next() {
		var c Conversation
		var lastDateUnix int64
		var subject sql.NullString
		err := rows.Scan(&c.Address, &c.ContactName, &subject, &c.LastMessage, &lastDateUnix, &c.MessageCount)
		if err != nil {
			return nil, err
		}
		c.LastDate = time.Unix(lastDateUnix, 0)
		c.Subject = subject.String
		c.Type = "conversation" // Changed from "message" or "call" to indicate it's a merged conversation
		conversations = append(conversations, c)
	}

	return conversations, nil
}

func formatCallType(callType int) string {
	switch callType {
	case 1:
		return "Incoming call"
	case 2:
		return "Outgoing call"
	case 3:
		return "Missed call"
	case 4:
		return "Voicemail"
	case 5:
		return "Rejected call"
	case 6:
		return "Refused call"
	default:
		return "Call"
	}
}

func GetMessages(userDB *sql.DB, address string, startDate, endDate *time.Time) ([]Message, error) {
	query := `
		SELECT id, address, body, type, date, read, thread_id,
		       COALESCE(subject, ''), COALESCE(media_type, ''), COALESCE(media_data, ''),
		       COALESCE(protocol, 0), COALESCE(status, 0), COALESCE(service_center, ''),
		       COALESCE(sub_id, 0), COALESCE(contact_name, ''), COALESCE(sender, ''),
		       COALESCE(content_type, ''), COALESCE(read_report, 0), COALESCE(read_status, 0),
		       COALESCE(message_id, ''), COALESCE(message_size, 0), COALESCE(message_type, 0),
		       COALESCE(sim_slot, 0), COALESCE(addresses, '')
		FROM messages
		WHERE record_type IN (1, 2) AND address = ?  -- 1 = SMS, 2 = MMS
	`

	args := []interface{}{address}
	if startDate != nil {
		query += " AND date >= ?"
		args = append(args, startDate.Unix())
	}
	if endDate != nil {
		query += " AND date <= ?"
		args = append(args, endDate.Unix())
	}

	query += " ORDER BY date ASC"

	slog.Debug("GetMessages: executing query", "address", address)
	slog.Debug("GetMessages: SQL query", "query", query)
	slog.Debug("GetMessages: query arguments", "args", args)

	rows, err := userDB.Query(query, args...)
	if err != nil {
		slog.Debug("GetMessages: Query error", "error", err)
		return nil, err
	}
	defer rows.Close()

	messages := []Message{}
	for rows.Next() {
		var m Message
		var dateUnix int64
		var readInt int
		var addressesStr string
		err := rows.Scan(&m.ID, &m.Address, &m.Body, &m.Type, &dateUnix,
			&readInt, &m.ThreadID, &m.Subject, &m.MediaType, &m.MediaData,
			&m.Protocol, &m.Status, &m.ServiceCenter, &m.SubID, &m.ContactName, &m.Sender,
			&m.ContentType, &m.ReadReport, &m.ReadStatus, &m.MessageID,
			&m.MessageSize, &m.MessageType, &m.SimSlot, &addressesStr)
		if err != nil {
			return nil, err
		}
		m.Date = time.Unix(dateUnix, 0)
		m.Read = readInt == 1

		// Parse addresses from comma-separated string
		if addressesStr != "" {
			m.Addresses = strings.Split(addressesStr, ",")
		}

		// Don't load media data - it will be fetched on demand via /api/media
		// Clear MediaData to save memory in response
		m.MediaData = nil

		slog.Debug("GetMessages: Message", "id", m.ID, "address", m.Address, "media_type", m.MediaType, "body", truncateString(m.Body, 50))

		messages = append(messages, m)
	}

	slog.Debug("GetMessages: Returning messages", "count", len(messages), "address", address)
	return messages, nil
}

func GetCallLogs(userDB *sql.DB, number string, startDate, endDate *time.Time) ([]CallLog, error) {
	query := `
		SELECT id, address, duration, date, type,
		       COALESCE(presentation, 0), COALESCE(subscription_id, ''), COALESCE(contact_name, '')
		FROM messages
		WHERE record_type = 3 AND address = ?  -- 3 = call
	`

	args := []interface{}{number}
	if startDate != nil {
		query += " AND date >= ?"
		args = append(args, startDate.Unix())
	}
	if endDate != nil {
		query += " AND date <= ?"
		args = append(args, endDate.Unix())
	}

	query += " ORDER BY date ASC"

	rows, err := userDB.Query(query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	calls := []CallLog{}
	for rows.Next() {
		var c CallLog
		var dateUnix int64
		err := rows.Scan(&c.ID, &c.Number, &c.Duration, &dateUnix, &c.Type,
			&c.Presentation, &c.SubscriptionID, &c.ContactName)
		if err != nil {
			return nil, err
		}
		c.Date = time.Unix(dateUnix, 0)
		calls = append(calls, c)
	}

	return calls, nil
}

func GetActivity(userDB *sql.DB, startDate, endDate *time.Time, limit, offset int) ([]ActivityItem, error) {
	return GetActivityByAddress(userDB, "", startDate, endDate, limit, offset)
}

func GetActivityByAddress(userDB *sql.DB, address string, startDate, endDate *time.Time, limit, offset int) ([]ActivityItem, error) {
	var activities []ActivityItem

	// Query from unified table
	query := `
		SELECT record_type, date, address, COALESCE(contact_name, '') as contact_name,
		       id, body, type, read, thread_id, COALESCE(subject, ''),
		       COALESCE(media_type, ''), COALESCE(media_data, ''),
		       COALESCE(protocol, 0), COALESCE(status, 0), COALESCE(service_center, ''),
		       COALESCE(sub_id, 0), COALESCE(content_type, ''), COALESCE(read_report, 0),
		       COALESCE(read_status, 0), COALESCE(message_id, ''), COALESCE(message_size, 0),
		       COALESCE(message_type, 0), COALESCE(sim_slot, 0), COALESCE(addresses, ''),
		       COALESCE(duration, 0), COALESCE(presentation, 0), COALESCE(subscription_id, ''),
		       COALESCE(sender, '')
		FROM messages
		WHERE 1=1
	`

	args := []interface{}{}
	if address != "" {
		query += " AND address = ?"
		args = append(args, address)
	}
	if startDate != nil {
		query += " AND date >= ?"
		args = append(args, startDate.Unix())
	}
	if endDate != nil {
		query += " AND date <= ?"
		args = append(args, endDate.Unix())
	}

	query += " ORDER BY date ASC LIMIT ? OFFSET ?"
	args = append(args, limit, offset)

	slog.Debug("GetActivityByAddress: executing query", "address", address, "limit", limit, "offset", offset)
	slog.Debug("GetActivityByAddress: SQL query", "query", query)
	slog.Debug("GetActivityByAddress: query arguments", "args", args)

	rows, err := userDB.Query(query, args...)
	if err != nil {
		slog.Debug("GetActivityByAddress: Query error", "error", err)
		return nil, err
	}
	defer rows.Close()

	for rows.Next() {
		var recordType int64
		var dateUnix int64
		var address, contactName string

		// Shared fields
		var id sql.NullInt64
		var itemType sql.NullInt64 // type field - used for both message type and call type

		// Message fields
		var body, subject, mediaType, serviceCenter, contentType, messageID, subscriptionID, addressesStr, sender sql.NullString
		var readInt, threadID, protocol, status, subID, readReport, readStatus, messageSize, messageTypeField, simSlot sql.NullInt64
		var mediaData []byte

		// Call fields
		var duration, presentation sql.NullInt64

		err := rows.Scan(&recordType, &dateUnix, &address, &contactName,
			&id, &body, &itemType, &readInt, &threadID, &subject,
			&mediaType, &mediaData,
			&protocol, &status, &serviceCenter,
			&subID, &contentType, &readReport,
			&readStatus, &messageID, &messageSize,
			&messageTypeField, &simSlot, &addressesStr,
			&duration, &presentation, &subscriptionID, &sender)
		if err != nil {
			return nil, err
		}

		var activityTypeStr string
		if recordType == 1 || recordType == 2 {
			// 1 = SMS, 2 = MMS
			activityTypeStr = "message"
		} else if recordType == 3 {
			// 3 = call
			activityTypeStr = "call"
		}

		activity := ActivityItem{
			Type:        activityTypeStr,
			Date:        time.Unix(dateUnix, 0),
			Address:     address,
			ContactName: contactName,
		}

		if (recordType == 1 || recordType == 2) && id.Valid {
			// Handle SMS (1) and MMS (2)
			msg := &Message{
				ID:            id.Int64,
				Address:       address,
				Body:          body.String,
				Date:          time.Unix(dateUnix, 0),
				ThreadID:      int(threadID.Int64),
				Subject:       subject.String,
				MediaType:     mediaType.String,
				MediaData:     mediaData,
				Protocol:      int(protocol.Int64),
				Status:        int(status.Int64),
				ServiceCenter: serviceCenter.String,
				SubID:         int(subID.Int64),
				ContactName:   contactName,
				ContentType:   contentType.String,
				ReadReport:    int(readReport.Int64),
				ReadStatus:    int(readStatus.Int64),
				MessageID:     messageID.String,
				MessageSize:   int(messageSize.Int64),
				MessageType:   int(messageTypeField.Int64),
				SimSlot:       int(simSlot.Int64),
				Sender:        sender.String,
			}
			if itemType.Valid {
				msg.Type = int(itemType.Int64)
			}
			if readInt.Valid {
				msg.Read = readInt.Int64 == 1
			}

			// Parse addresses from comma-separated string
			slog.Debug("GetActivityByAddress: addressesStr raw", "id", id.Int64, "valid", addressesStr.Valid, "value", addressesStr.String)
			if addressesStr.Valid && addressesStr.String != "" {
				msg.Addresses = strings.Split(addressesStr.String, ",")
				slog.Debug("GetActivityByAddress: addresses split result", "id", id.Int64, "count", len(msg.Addresses), "values", msg.Addresses)
			} else if strings.Contains(address, ",") {
				// Fallback: If addresses field is empty but address contains commas,
				// this is a group conversation - parse the address field
				msg.Addresses = strings.Split(address, ",")
				slog.Debug("GetActivityByAddress: addresses from address field", "id", id.Int64, "count", len(msg.Addresses), "values", msg.Addresses)
			}

			// Don't load media data - it will be fetched on demand via /api/media
			// Clear MediaData to save memory in response
			msg.MediaData = nil

			slog.Debug("GetActivityByAddress: Message", "id", msg.ID, "address", msg.Address, "type", msg.Type, "sender", msg.Sender, "addresses", msg.Addresses, "media_type", msg.MediaType, "body", truncateString(msg.Body, 50))

			activity.Message = msg
		} else if recordType == 3 && id.Valid {
			// Handle calls (3)
			call := &CallLog{
				ID:             id.Int64,
				Number:         address,
				Duration:       int(duration.Int64),
				Date:           time.Unix(dateUnix, 0),
				Type:           int(itemType.Int64),
				Presentation:   int(presentation.Int64),
				SubscriptionID: subscriptionID.String,
				ContactName:    contactName,
			}
			slog.Debug("GetActivityByAddress: Call", "id", call.ID, "number", call.Number, "type", call.Type, "duration", call.Duration)
			activity.Call = call
		}

		activities = append(activities, activity)
	}

	slog.Debug("GetActivityByAddress: Returning activities", "count", len(activities), "address", address)
	return activities, nil
}

func GetMessageMedia(userDB *sql.DB, messageID string) ([]byte, string, error) {
	query := `
		SELECT COALESCE(media_data, ''), COALESCE(media_type, '')
		FROM messages
		WHERE id = ? AND record_type IN (1, 2)  -- 1 = SMS, 2 = MMS
	`

	slog.Debug("GetMessageMedia: Fetching media", "message_id", messageID)
	slog.Debug("GetMessageMedia: SQL query", "query", query)

	var mediaData []byte
	var mediaType string

	err := userDB.QueryRow(query, messageID).Scan(&mediaData, &mediaType)
	if err != nil {
		slog.Debug("GetMessageMedia: Error scanning row", "message_id", messageID, "error", err)
		return nil, "", err
	}

	slog.Debug("GetMessageMedia: Found media", "media_type", mediaType, "data_length", len(mediaData), "message_id", messageID)

	if len(mediaData) == 0 || mediaType == "" {
		slog.Debug("GetMessageMedia: No media found", "message_id", messageID)
		return nil, "", fmt.Errorf("no media found")
	}

	// Convert HEIC to JPEG if needed
	if isHEICContentType(mediaType) {
		convertedData, err := convertHEICtoJPEG(mediaData)
		if err != nil {
			slog.Error("Failed to convert HEIC to JPEG", "message_id", messageID, "error", err)
			// Return original if conversion fails
			return mediaData, mediaType, nil
		}
		return convertedData, "image/jpeg", nil
	}

	// Convert unsupported video formats (3GP, etc.) to MP4 if needed
	if needsVideoConversion(mediaType) {
		slog.Info("Converting video to MP4", "from_type", mediaType, "message_id", messageID)
		convertedData, err := convertVideoToMP4(mediaData)
		if err != nil {
			slog.Error("Failed to convert video to MP4", "message_id", messageID, "error", err)
			// Return original if conversion fails
			return mediaData, mediaType, nil
		}
		slog.Info("Successfully converted video to MP4", "message_id", messageID)
		return convertedData, "video/mp4", nil
	}

	return mediaData, mediaType, nil
}

func GetDateRange(userDB *sql.DB) (time.Time, time.Time, error) {
	var minDate, maxDate int64

	// Get min/max from unified messages table
	query := "SELECT MIN(date), MAX(date) FROM messages"
	var min, max sql.NullInt64
	err := userDB.QueryRow(query).Scan(&min, &max)
	if err != nil && err != sql.ErrNoRows {
		return time.Time{}, time.Time{}, err
	}

	if !min.Valid || !max.Valid {
		return time.Time{}, time.Time{}, fmt.Errorf("no data available")
	}

	minDate = min.Int64
	maxDate = max.Int64

	return time.Unix(minDate, 0), time.Unix(maxDate, 0), nil
}

// SearchResult represents a message search result
type SearchResult struct {
	MessageID   int64     `json:"message_id"`
	Address     string    `json:"address"`
	ContactName string    `json:"contact_name"`
	Body        string    `json:"body"`
	Date        time.Time `json:"date"`
	Snippet     string    `json:"snippet"`
}

// SearchMessages performs full-text search on message contents
func SearchMessages(userDB *sql.DB, query string, limit int) ([]SearchResult, error) {
	if query == "" {
		return []SearchResult{}, nil
	}

	sqlQuery := `
		SELECT
			m.id,
			m.address,
			COALESCE(m.contact_name, ''),
			m.body,
			m.date,
			snippet(messages_fts, 2, '<mark>', '</mark>', '...', 50) as snippet
		FROM messages_fts
		JOIN messages m ON messages_fts.rowid = m.id
		WHERE messages_fts MATCH ?
		ORDER BY rank
		LIMIT ?
	`

	rows, err := userDB.Query(sqlQuery, query, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	results := []SearchResult{}
	for rows.Next() {
		var r SearchResult
		var dateUnix int64
		err := rows.Scan(&r.MessageID, &r.Address, &r.ContactName, &r.Body, &dateUnix, &r.Snippet)
		if err != nil {
			return nil, err
		}
		r.Date = time.Unix(dateUnix, 0)
		results = append(results, r)
	}

	return results, nil
}
