package sqlite

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"time"

	"chat-switchboard/models"
	"chat-switchboard/store"
)

// ── Tree Operations (v0.29.0) ───────────────────────────────────────────

func (s *MessageStore) GetActiveLeaf(ctx context.Context, channelID, userID string) (*string, error) {
	var leafID *string

	err := DB.QueryRowContext(ctx, `
		SELECT active_leaf_id FROM channel_cursors
		WHERE channel_id = ? AND user_id = ?
	`, channelID, userID).Scan(&leafID)

	if err == nil && leafID != nil {
		var exists bool
		DB.QueryRowContext(ctx, `
			SELECT EXISTS(SELECT 1 FROM messages WHERE id = ? AND deleted_at IS NULL)
		`, *leafID).Scan(&exists)
		if exists {
			return leafID, nil
		}
	}

	var fallbackID string
	err = DB.QueryRowContext(ctx, `
		SELECT id FROM messages
		WHERE channel_id = ? AND deleted_at IS NULL
		ORDER BY created_at DESC LIMIT 1
	`, channelID).Scan(&fallbackID)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &fallbackID, nil
}

func (s *MessageStore) GetPathToLeaf(ctx context.Context, channelID, leafID string) ([]store.PathMessage, error) {
	rows, err := DB.QueryContext(ctx, `
		WITH RECURSIVE path AS (
			SELECT id, parent_id, role, content, model, tokens_used, tool_calls, metadata,
			       participant_type, participant_id, sibling_index, created_at,
			       0 AS depth
			FROM messages
			WHERE id = ? AND channel_id = ? AND deleted_at IS NULL

			UNION ALL

			SELECT m.id, m.parent_id, m.role, m.content, m.model, m.tokens_used, m.tool_calls, m.metadata,
			       m.participant_type, m.participant_id, m.sibling_index, m.created_at,
			       p.depth + 1
			FROM messages m
			JOIN path p ON m.id = p.parent_id
			WHERE m.deleted_at IS NULL
		)
		SELECT id, parent_id, role, content, model, tokens_used, tool_calls, metadata,
		       participant_type, participant_id, sibling_index, created_at
		FROM path
		ORDER BY depth DESC
	`, leafID, channelID)
	if err != nil {
		return nil, fmt.Errorf("GetPathToLeaf: %w", err)
	}
	defer rows.Close()

	var path []store.PathMessage
	for rows.Next() {
		var m store.PathMessage
		var participantType, participantID sql.NullString
		var toolCallsJSON, metadataJSON []byte
		if err := rows.Scan(
			&m.ID, &m.ParentID, &m.Role, &m.Content, &m.Model, &m.TokensUsed,
			&toolCallsJSON, &metadataJSON,
			&participantType, &participantID, &m.SiblingIndex, &m.CreatedAt,
		); err != nil {
			return nil, fmt.Errorf("GetPathToLeaf scan: %w", err)
		}
		if len(toolCallsJSON) > 0 && string(toolCallsJSON) != "null" {
			raw := json.RawMessage(toolCallsJSON)
			m.ToolCalls = &raw
		}
		if len(metadataJSON) > 0 && string(metadataJSON) != "null" && string(metadataJSON) != "{}" {
			raw := json.RawMessage(metadataJSON)
			m.Metadata = &raw
		}
		if participantType.Valid {
			m.ParticipantType = participantType.String
		}
		if participantID.Valid {
			m.ParticipantID = participantID.String
		}
		path = append(path, m)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}

	for i := range path {
		count, _ := s.GetSiblingCount(ctx, channelID, path[i].ParentID)
		path[i].SiblingCount = count
	}

	_ = s.ResolveSenderInfo(ctx, path)

	return path, nil
}

func (s *MessageStore) GetActivePath(ctx context.Context, channelID, userID string) ([]store.PathMessage, error) {
	leafID, err := s.GetActiveLeaf(ctx, channelID, userID)
	if err != nil {
		return nil, err
	}
	if leafID == nil {
		return []store.PathMessage{}, nil
	}
	return s.GetPathToLeaf(ctx, channelID, *leafID)
}

func (s *MessageStore) GetSiblingsList(ctx context.Context, messageID string) ([]store.SiblingInfo, int, error) {
	var parentID *string
	var channelID string
	err := DB.QueryRowContext(ctx, `
		SELECT parent_id, channel_id FROM messages
		WHERE id = ? AND deleted_at IS NULL
	`, messageID).Scan(&parentID, &channelID)
	if err != nil {
		return nil, 0, fmt.Errorf("message not found: %w", err)
	}

	var rows *sql.Rows
	if parentID == nil {
		rows, err = DB.QueryContext(ctx, `
			SELECT id, role, model, sibling_index, SUBSTR(content, 1, 80), created_at
			FROM messages
			WHERE channel_id = ? AND parent_id IS NULL AND deleted_at IS NULL
			ORDER BY sibling_index, created_at
		`, channelID)
	} else {
		rows, err = DB.QueryContext(ctx, `
			SELECT id, role, model, sibling_index, SUBSTR(content, 1, 80), created_at
			FROM messages
			WHERE parent_id = ? AND deleted_at IS NULL
			ORDER BY sibling_index, created_at
		`, *parentID)
	}
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	var siblings []store.SiblingInfo
	currentIdx := 0
	for i := 0; rows.Next(); i++ {
		var si store.SiblingInfo
		if err := rows.Scan(&si.ID, &si.Role, &si.Model, &si.SiblingIndex, &si.Preview, &si.CreatedAt); err != nil {
			return nil, 0, err
		}
		if si.ID == messageID {
			currentIdx = i
		}
		siblings = append(siblings, si)
	}

	return siblings, currentIdx, rows.Err()
}

func (s *MessageStore) GetSiblingCount(ctx context.Context, channelID string, parentID *string) (int, error) {
	var count int
	var err error
	if parentID == nil {
		err = DB.QueryRowContext(ctx, `
			SELECT COUNT(*) FROM messages
			WHERE channel_id = ? AND parent_id IS NULL AND deleted_at IS NULL
		`, channelID).Scan(&count)
	} else {
		err = DB.QueryRowContext(ctx, `
			SELECT COUNT(*) FROM messages
			WHERE parent_id = ? AND deleted_at IS NULL
		`, *parentID).Scan(&count)
	}
	if err != nil || count == 0 {
		return 1, nil
	}
	return count, nil
}

func (s *MessageStore) FindLeafFromMessage(ctx context.Context, messageID string) (string, error) {
	var leafID string
	err := DB.QueryRowContext(ctx, `
		WITH RECURSIVE descendants AS (
			SELECT id, 0 AS depth
			FROM messages
			WHERE id = ? AND deleted_at IS NULL

			UNION ALL

			SELECT child.id, d.depth + 1
			FROM messages child
			JOIN descendants d ON child.parent_id = d.id
			WHERE child.deleted_at IS NULL
			  AND child.sibling_index = (
			      SELECT MIN(sibling_index) FROM messages
			      WHERE parent_id = d.id AND deleted_at IS NULL
			  )
		)
		SELECT id FROM descendants
		ORDER BY depth DESC
		LIMIT 1
	`, messageID).Scan(&leafID)

	if err != nil {
		return messageID, nil
	}
	return leafID, nil
}

func (s *MessageStore) NextSiblingIndexForParent(ctx context.Context, channelID string, parentID *string) (int, error) {
	var maxIdx sql.NullInt64
	var err error
	if parentID == nil {
		err = DB.QueryRowContext(ctx, `
			SELECT MAX(sibling_index) FROM messages
			WHERE channel_id = ? AND parent_id IS NULL AND deleted_at IS NULL
		`, channelID).Scan(&maxIdx)
	} else {
		err = DB.QueryRowContext(ctx, `
			SELECT MAX(sibling_index) FROM messages
			WHERE parent_id = ? AND deleted_at IS NULL
		`, *parentID).Scan(&maxIdx)
	}
	if err != nil || !maxIdx.Valid {
		return 0, nil
	}
	return int(maxIdx.Int64) + 1, nil
}

func (s *MessageStore) HasPersonaMessages(ctx context.Context, channelID string) (bool, error) {
	var id string
	err := DB.QueryRowContext(ctx, `
		SELECT id FROM messages
		WHERE channel_id = ? AND role = 'assistant' AND participant_type = 'persona'
		LIMIT 1
	`, channelID).Scan(&id)
	return err == nil && id != "", nil
}

func (s *MessageStore) CreateWithCursor(ctx context.Context, m *models.Message, cursorUserID string) error {
	m.ID = store.NewID()
	now := time.Now().UTC()
	m.CreatedAt = now

	// tool_calls: serialize for SQLite
	var tcVal interface{}
	if m.ToolCalls != nil {
		tcVal = ToJSON(m.ToolCalls)
	}

	_, err := DB.ExecContext(ctx, `
		INSERT INTO messages (id, channel_id, role, content, model, tokens_used,
		                      tool_calls, parent_id, participant_type, participant_id,
		                      provider_config_id, sibling_index, created_at)
		VALUES (?, ?, ?, ?, NULLIF(?, ''), ?, ?, ?, ?, ?, NULLIF(?, ''), ?, ?)
	`, m.ID, m.ChannelID, m.Role, m.Content, safeModelStr(m.Model), m.TokensUsed,
		tcVal, models.NullString(m.ParentID),
		m.ParticipantType, m.ParticipantID,
		safeStringPtr(m.ProviderConfigID), m.SiblingIndex,
		now.Format(timeFmt),
	)
	if err != nil {
		return fmt.Errorf("CreateWithCursor insert: %w", err)
	}

	// Update cursor
	if cursorUserID != "" {
		_, _ = DB.ExecContext(ctx, `
			INSERT INTO channel_cursors (id, channel_id, user_id, active_leaf_id)
			VALUES (?, ?, ?, ?)
			ON CONFLICT (channel_id, user_id) DO UPDATE SET
				active_leaf_id = excluded.active_leaf_id, updated_at = datetime('now')
		`, store.NewID(), m.ChannelID, cursorUserID, m.ID)
	}

	// Touch channel
	_, _ = DB.ExecContext(ctx, `UPDATE channels SET updated_at = datetime('now') WHERE id = ?`, m.ChannelID)

	return nil
}

func (s *MessageStore) ResolveSenderInfo(ctx context.Context, path []store.PathMessage) error {
	userIDs := map[string]bool{}
	personaIDs := map[string]bool{}
	for _, m := range path {
		if m.ParticipantID == "" {
			continue
		}
		switch m.ParticipantType {
		case "user":
			userIDs[m.ParticipantID] = true
		case "persona":
			personaIDs[m.ParticipantID] = true
		}
	}

	userNames := map[string]string{}
	userAvatars := map[string]string{}
	for uid := range userIDs {
		var name, avatar sql.NullString
		_ = DB.QueryRowContext(ctx, `
			SELECT COALESCE(display_name, username), avatar_url FROM users WHERE id = ?
		`, uid).Scan(&name, &avatar)
		if name.Valid {
			userNames[uid] = name.String
		}
		if avatar.Valid {
			userAvatars[uid] = avatar.String
		}
	}

	personaNames := map[string]string{}
	personaAvatars := map[string]string{}
	for pid := range personaIDs {
		var name, avatar sql.NullString
		_ = DB.QueryRowContext(ctx, `
			SELECT name, avatar FROM personas WHERE id = ?
		`, pid).Scan(&name, &avatar)
		if name.Valid {
			personaNames[pid] = name.String
		}
		if avatar.Valid {
			personaAvatars[pid] = avatar.String
		}
	}

	for i := range path {
		pid := path[i].ParticipantID
		switch path[i].ParticipantType {
		case "user":
			if n, ok := userNames[pid]; ok {
				path[i].SenderName = &n
			}
			if a, ok := userAvatars[pid]; ok && a != "" {
				path[i].SenderAvatar = &a
			}
		case "persona":
			if n, ok := personaNames[pid]; ok {
				path[i].SenderName = &n
			}
			if a, ok := personaAvatars[pid]; ok && a != "" {
				path[i].SenderAvatar = &a
			}
		}
	}

	return nil
}

// ── helpers ─────────────────────────────────

func safeModelStr(m string) string {
	if m == "" {
		return ""
	}
	return m
}

func safeStringPtr(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}
