package collaboration

import (
	"context"
	"database/sql"
	"fmt"
	"time"
)

type Repository struct {
	db *sql.DB
}

func NewRepository(db *sql.DB) *Repository {
	return &Repository{db: db}
}

// Comments

func (r *Repository) CreateComment(ctx context.Context, comment Comment) error {
	_, err := r.db.ExecContext(ctx, `
		INSERT INTO comments (id, document_id, version_hash, parent_id, author_id, content, anchor_hash, anchor_line, created_at, resolved_at, resolved_by)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, comment.ID, comment.DocumentID, comment.VersionHash, comment.ParentID, comment.AuthorID, comment.Content, comment.AnchorHash, comment.AnchorLine, formatTime(comment.CreatedAt), comment.ResolvedAt, comment.ResolvedBy)
	if err != nil {
		return fmt.Errorf("insert comment: %w", err)
	}
	return nil
}

func (r *Repository) GetComment(ctx context.Context, id string) (*Comment, error) {
	var c Comment
	var parentID, anchorHash sql.NullString
	var anchorLine sql.NullInt64
	var resolvedAt, resolvedBy sql.NullString
	var createdAt string

	err := r.db.QueryRowContext(ctx, `
		SELECT c.id, c.document_id, c.version_hash, c.parent_id, c.author_id, COALESCE(u.display_name, u.email), c.content, c.anchor_hash, c.anchor_line, c.created_at, c.resolved_at, c.resolved_by
		FROM comments c
		JOIN users u ON u.id = c.author_id
		WHERE c.id = ?
	`, id).Scan(
		&c.ID, &c.DocumentID, &c.VersionHash, &parentID, &c.AuthorID, &c.AuthorName,
		&c.Content, &anchorHash, &anchorLine, &createdAt, &resolvedAt, &resolvedBy,
	)
	if err != nil {
		return nil, err
	}

	c.ParentID = nullString(parentID.String)
	c.AnchorHash = nullString(anchorHash.String)
	if anchorLine.Valid {
		c.AnchorLine = ptr(int(anchorLine.Int64))
	}
	c.CreatedAt, _ = parseTime(createdAt)
	if resolvedAt.Valid && resolvedAt.String != "" {
		t, _ := parseTime(resolvedAt.String)
		c.ResolvedAt = &t
		c.ResolvedBy = nullString(resolvedBy.String)
	}

	return &c, nil
}

func (r *Repository) ListCommentsByDocument(ctx context.Context, documentID string, includeResolved bool) ([]Comment, error) {
	query := `
		SELECT c.id, c.document_id, c.version_hash, c.parent_id, c.author_id, COALESCE(u.display_name, u.email), c.content, c.anchor_hash, c.anchor_line, c.created_at, c.resolved_at, c.resolved_by
		FROM comments c
		JOIN users u ON u.id = c.author_id
		WHERE c.document_id = ?`
	if !includeResolved {
		query += ` AND c.resolved_at IS NULL`
	}
	query += ` ORDER BY c.created_at ASC`
	rows, err := r.db.QueryContext(ctx, query, documentID)
	if err != nil {
		return nil, fmt.Errorf("list comments: %w", err)
	}
	defer rows.Close()

	return scanComments(rows)
}

func (r *Repository) ListCommentsByAnchor(ctx context.Context, documentID string, anchorHash string, includeResolved bool) ([]Comment, error) {
	query := `
		SELECT c.id, c.document_id, c.version_hash, c.parent_id, c.author_id, COALESCE(u.display_name, u.email), c.content, c.anchor_hash, c.anchor_line, c.created_at, c.resolved_at, c.resolved_by
		FROM comments c
		JOIN users u ON u.id = c.author_id
		WHERE c.document_id = ? AND c.anchor_hash = ?`
	if !includeResolved {
		query += ` AND c.resolved_at IS NULL`
	}
	query += ` ORDER BY c.created_at ASC`
	rows, err := r.db.QueryContext(ctx, query, documentID, anchorHash)
	if err != nil {
		return nil, fmt.Errorf("list comments by anchor: %w", err)
	}
	defer rows.Close()

	return scanComments(rows)
}

func (r *Repository) ResolveCommentThread(ctx context.Context, commentID string, userID string) error {
	result, err := r.db.ExecContext(ctx, `
		WITH RECURSIVE thread(id) AS (
			SELECT id FROM comments WHERE id = ? AND parent_id IS NULL
			UNION ALL
			SELECT c.id FROM comments c JOIN thread t ON c.parent_id = t.id
		)
		UPDATE comments SET resolved_at = ?, resolved_by = ?
		WHERE id IN (SELECT id FROM thread) AND resolved_at IS NULL
	`, commentID, formatTime(time.Now().UTC()), userID)
	if err != nil {
		return fmt.Errorf("resolve comment thread: %w", err)
	}
	changed, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("resolve comment thread result: %w", err)
	}
	if changed == 0 {
		return fmt.Errorf("comment thread not found or already resolved")
	}
	return nil
}

func scanComments(rows *sql.Rows) ([]Comment, error) {
	var comments []Comment
	for rows.Next() {
		var c Comment
		var parentID, anchorHash sql.NullString
		var anchorLine sql.NullInt64
		var resolvedAt, resolvedBy sql.NullString
		var createdAt string

		if err := rows.Scan(
			&c.ID, &c.DocumentID, &c.VersionHash, &parentID, &c.AuthorID, &c.AuthorName,
			&c.Content, &anchorHash, &anchorLine, &createdAt, &resolvedAt, &resolvedBy,
		); err != nil {
			return nil, fmt.Errorf("scan comment: %w", err)
		}

		c.ParentID = nullString(parentID.String)
		c.AnchorHash = nullString(anchorHash.String)
		if anchorLine.Valid {
			c.AnchorLine = ptr(int(anchorLine.Int64))
		}
		c.CreatedAt, _ = parseTime(createdAt)
		if resolvedAt.Valid && resolvedAt.String != "" {
			t, _ := parseTime(resolvedAt.String)
			c.ResolvedAt = &t
			c.ResolvedBy = nullString(resolvedBy.String)
		}
		comments = append(comments, c)
	}
	return comments, rows.Err()
}

// Notifications

func (r *Repository) CreateNotification(ctx context.Context, n Notification) error {
	_, err := r.db.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, resource_type, resource_id, message, read_at, emailed_at, created_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, n.ID, n.UserID, n.Type, n.ResourceType, n.ResourceID, n.Message, n.ReadAt, n.EmailedAt, formatTime(n.CreatedAt))
	if err != nil {
		return fmt.Errorf("insert notification: %w", err)
	}
	return nil
}

func (r *Repository) ListNotifications(ctx context.Context, userID string, unreadOnly bool, limit int) ([]Notification, error) {
	query := `
		SELECT id, user_id, type, resource_type, resource_id, message, read_at, emailed_at, created_at
		FROM notifications
		WHERE user_id = ?`
	args := []any{userID}
	if unreadOnly {
		query += ` AND read_at IS NULL`
	}
	query += ` ORDER BY created_at DESC`
	if limit > 0 {
		query += ` LIMIT ?`
		args = append(args, limit)
	}

	rows, err := r.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("list notifications: %w", err)
	}
	defer rows.Close()

	var notifications []Notification
	for rows.Next() {
		var n Notification
		var readAt, emailedAt sql.NullString
		var createdAt string
		if err := rows.Scan(&n.ID, &n.UserID, &n.Type, &n.ResourceType, &n.ResourceID, &n.Message, &readAt, &emailedAt, &createdAt); err != nil {
			return nil, fmt.Errorf("scan notification: %w", err)
		}
		if readAt.Valid && readAt.String != "" {
			t, _ := parseTime(readAt.String)
			n.ReadAt = &t
		}
		if emailedAt.Valid && emailedAt.String != "" {
			t, _ := parseTime(emailedAt.String)
			n.EmailedAt = &t
		}
		n.CreatedAt, _ = parseTime(createdAt)
		notifications = append(notifications, n)
	}
	return notifications, rows.Err()
}

func (r *Repository) CountUnreadNotifications(ctx context.Context, userID string) (int, error) {
	var count int
	err := r.db.QueryRowContext(ctx, `
		SELECT COUNT(*) FROM notifications WHERE user_id = ? AND read_at IS NULL
	`, userID).Scan(&count)
	if err != nil {
		return 0, fmt.Errorf("count unread notifications: %w", err)
	}
	return count, nil
}

func (r *Repository) MarkNotificationRead(ctx context.Context, notificationID string, userID string) error {
	_, err := r.db.ExecContext(ctx, `
		UPDATE notifications SET read_at = ? WHERE id = ? AND user_id = ?
	`, formatTime(time.Now().UTC()), notificationID, userID)
	if err != nil {
		return fmt.Errorf("mark notification read: %w", err)
	}
	return nil
}

func (r *Repository) MarkAllNotificationsRead(ctx context.Context, userID string) error {
	_, err := r.db.ExecContext(ctx, `
		UPDATE notifications SET read_at = ? WHERE user_id = ? AND read_at IS NULL
	`, formatTime(time.Now().UTC()), userID)
	if err != nil {
		return fmt.Errorf("mark all notifications read: %w", err)
	}
	return nil
}

// Watchers

func (r *Repository) AddWatcher(ctx context.Context, w Watcher) error {
	if (w.DocumentID == nil) == (w.FolderPath == nil) {
		return fmt.Errorf("watcher requires exactly one target")
	}
	_, err := r.db.ExecContext(ctx, `
		INSERT OR IGNORE INTO watchers (user_id, document_id, folder_path, created_at)
		VALUES (?, ?, ?, ?)
	`, w.UserID, w.DocumentID, w.FolderPath, formatTime(w.CreatedAt))
	if err != nil {
		return fmt.Errorf("add watcher: %w", err)
	}
	return nil
}

func (r *Repository) RemoveWatcher(ctx context.Context, userID string, documentID *string, folderPath *string) error {
	_, err := r.db.ExecContext(ctx, `
		DELETE FROM watchers WHERE user_id = ? AND document_id IS ? AND folder_path IS ?
	`, userID, documentID, folderPath)
	if err != nil {
		return fmt.Errorf("remove watcher: %w", err)
	}
	return nil
}

func (r *Repository) ListWatchersByDocument(ctx context.Context, documentID string) ([]Watcher, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT user_id, document_id, folder_path, created_at
		FROM watchers
		WHERE document_id = ?
	`, documentID)
	if err != nil {
		return nil, fmt.Errorf("list document watchers: %w", err)
	}
	defer rows.Close()
	return scanWatchers(rows)
}

func (r *Repository) ListWatchersByFolder(ctx context.Context, documentPath string) ([]Watcher, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT user_id, document_id, folder_path, created_at
		FROM watchers
		WHERE folder_path = '' OR folder_path || '/' = substr(?, 1, length(folder_path) + 1)
	`, documentPath)
	if err != nil {
		return nil, fmt.Errorf("list folder watchers: %w", err)
	}
	defer rows.Close()
	return scanWatchers(rows)
}

func (r *Repository) ListWatchersForDocument(ctx context.Context, documentID string) ([]Watcher, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT DISTINCT w.user_id, w.document_id, w.folder_path, w.created_at
		FROM watchers w
		JOIN documents d ON d.id = ?
		WHERE w.document_id = d.id
		   OR (w.folder_path IS NOT NULL AND (
		       w.folder_path = ''
		       OR w.folder_path || '/' = substr(d.path, 1, length(w.folder_path) + 1)
		   ))
	`, documentID)
	if err != nil {
		return nil, fmt.Errorf("list watchers for document: %w", err)
	}
	defer rows.Close()
	return scanWatchers(rows)
}

func (r *Repository) IsWatching(ctx context.Context, userID string, documentID string) (bool, error) {
	var count int
	err := r.db.QueryRowContext(ctx, `
		SELECT COUNT(*) FROM watchers WHERE user_id = ? AND document_id = ?
	`, userID, documentID).Scan(&count)
	if err != nil {
		return false, fmt.Errorf("check watching: %w", err)
	}
	return count > 0, nil
}

func scanWatchers(rows *sql.Rows) ([]Watcher, error) {
	var watchers []Watcher
	for rows.Next() {
		var w Watcher
		var docID, folderPath sql.NullString
		var createdAt string
		if err := rows.Scan(&w.UserID, &docID, &folderPath, &createdAt); err != nil {
			return nil, fmt.Errorf("scan watcher: %w", err)
		}
		w.DocumentID = nullString(docID.String)
		w.FolderPath = nullString(folderPath.String)
		w.CreatedAt, _ = parseTime(createdAt)
		watchers = append(watchers, w)
	}
	return watchers, rows.Err()
}
