package kanban

import (
	"context"
	"time"
	"tm/pkg/logger"
	orm "tm/pkg/mongo"

	"go.mongodb.org/mongo-driver/v2/bson"
)

// BoardRepository interface defines board data access methods
type BoardRepository interface {
	// Board CRUD operations
	CreateBoard(ctx context.Context, board *Board) error
	GetBoardByID(ctx context.Context, id string) (*Board, error)
	GetBoardsByUserID(ctx context.Context, userID string) ([]Board, error)
	GetDefaultBoard(ctx context.Context, userID string) (*Board, error)
	UpdateBoard(ctx context.Context, board *Board) error
	DeleteBoard(ctx context.Context, id string) error
}

// ColumnRepository interface defines column data access methods
type ColumnRepository interface {
	// Column CRUD operations
	CreateColumn(ctx context.Context, column *Column) error
	GetColumnByID(ctx context.Context, id string) (*Column, error)
	GetColumnsByBoardID(ctx context.Context, boardID string) ([]Column, error)
	UpdateColumn(ctx context.Context, column *Column) error
	DeleteColumn(ctx context.Context, id string) error
	UpdateColumnOrder(ctx context.Context, boardID string, columnOrders map[string]int) error
}

// CardRepository interface defines card data access methods
type CardRepository interface {
	// Card CRUD operations
	CreateCard(ctx context.Context, card *Card) error
	GetCardByID(ctx context.Context, id string) (*Card, error)
	GetCardsByColumnID(ctx context.Context, columnID string) ([]Card, error)
	GetCardsByTenderID(ctx context.Context, tenderID string) ([]Card, error)
	UpdateCard(ctx context.Context, card *Card) error
	DeleteCard(ctx context.Context, id string) error
	MoveCard(ctx context.Context, cardID, targetColumnID string, newOrder int) error
	UpdateCardOrder(ctx context.Context, columnID string, cardOrders map[string]int) error
}

func boardCollectionName() string {
	return "kanban_boards"
}

func columnCollectionName() string {
	return "kanban_columns"
}

func cardCollectionName() string {
	return "kanban_cards"
}

// boardRepository implements BoardRepository interface using MongoDB ORM
type boardRepository struct {
	ormRepo      orm.Repository[Board]
	mongoManager *orm.ConnectionManager
	logger       logger.Logger
}

// columnRepository implements ColumnRepository interface using MongoDB ORM
type columnRepository struct {
	ormRepo      orm.Repository[Column]
	mongoManager *orm.ConnectionManager
	logger       logger.Logger
}

// cardRepository implements CardRepository interface using MongoDB ORM
type cardRepository struct {
	ormRepo      orm.Repository[Card]
	mongoManager *orm.ConnectionManager
	logger       logger.Logger
}

// NewBoardRepository creates a new board repository
func NewBoardRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) BoardRepository {
	// Create indexes for boards collection
	boardIndexes := []orm.Index{
		*orm.NewIndex("user_id_idx", bson.D{{Key: "user_id", Value: 1}}),
		*orm.NewIndex("is_default_idx", bson.D{{Key: "is_default", Value: 1}}),
		*orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
		*orm.NewIndex("user_default_idx", bson.D{
			{Key: "user_id", Value: 1},
			{Key: "is_default", Value: 1},
		}),
	}

	// Create indexes
	if err := mongoManager.CreateIndexes(boardCollectionName(), boardIndexes); err != nil {
		logger.Warn("Failed to create board indexes", map[string]interface{}{
			"error": err.Error(),
		})
	}

	boardOrmRepo := orm.NewRepository[Board](mongoManager.GetCollection(boardCollectionName()), logger)

	return &boardRepository{
		ormRepo:      boardOrmRepo,
		mongoManager: mongoManager,
		logger:       logger,
	}
}

// NewColumnRepository creates a new column repository
func NewColumnRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) ColumnRepository {
	// Create indexes for columns collection
	columnIndexes := []orm.Index{
		*orm.NewIndex("board_id_idx", bson.D{{Key: "board_id", Value: 1}}),
		*orm.NewIndex("order_idx", bson.D{{Key: "order", Value: 1}}),
		*orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
		*orm.NewIndex("board_order_idx", bson.D{
			{Key: "board_id", Value: 1},
			{Key: "order", Value: 1},
		}),
	}

	// Create indexes
	if err := mongoManager.CreateIndexes(columnCollectionName(), columnIndexes); err != nil {
		logger.Warn("Failed to create column indexes", map[string]interface{}{
			"error": err.Error(),
		})
	}

	columnOrmRepo := orm.NewRepository[Column](mongoManager.GetCollection(columnCollectionName()), logger)

	return &columnRepository{
		ormRepo:      columnOrmRepo,
		mongoManager: mongoManager,
		logger:       logger,
	}
}

// NewCardRepository creates a new card repository
func NewCardRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) CardRepository {
	// Create indexes for cards collection
	cardIndexes := []orm.Index{
		*orm.NewIndex("column_id_idx", bson.D{{Key: "column_id", Value: 1}}),
		*orm.NewIndex("tender_id_idx", bson.D{{Key: "tender_id", Value: 1}}),
		*orm.NewIndex("order_idx", bson.D{{Key: "order", Value: 1}}),
		*orm.NewIndex("priority_idx", bson.D{{Key: "priority", Value: 1}}),
		*orm.NewIndex("due_date_idx", bson.D{{Key: "due_date", Value: 1}}),
		*orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
		*orm.NewIndex("column_order_idx", bson.D{
			{Key: "column_id", Value: 1},
			{Key: "order", Value: 1},
		}),
	}

	// Create indexes
	if err := mongoManager.CreateIndexes(cardCollectionName(), cardIndexes); err != nil {
		logger.Warn("Failed to create card indexes", map[string]interface{}{
			"error": err.Error(),
		})
	}

	cardOrmRepo := orm.NewRepository[Card](mongoManager.GetCollection(cardCollectionName()), logger)

	return &cardRepository{
		ormRepo:      cardOrmRepo,
		mongoManager: mongoManager,
		logger:       logger,
	}
}

// BoardRepository implementation

func (r *boardRepository) CreateBoard(ctx context.Context, board *Board) error {
	now := time.Now().Unix()
	board.SetCreatedAt(now)

	err := r.ormRepo.Create(ctx, board)
	if err != nil {
		r.logger.Error("Failed to create board", map[string]interface{}{
			"error":   err.Error(),
			"name":    board.Name,
			"user_id": board.UserID,
		})
		return err
	}

	r.logger.Info("Board created successfully", map[string]interface{}{
		"board_id": board.ID,
		"name":     board.Name,
		"user_id":  board.UserID,
	})

	return nil
}

func (r *boardRepository) GetBoardByID(ctx context.Context, id string) (*Board, error) {
	board, err := r.ormRepo.FindByID(ctx, id)
	if err != nil {
		r.logger.Error("Failed to get board by ID", map[string]interface{}{
			"board_id": id,
			"error":    err.Error(),
		})
		return nil, err
	}

	return board, nil
}

func (r *boardRepository) GetBoardsByUserID(ctx context.Context, userID string) ([]Board, error) {
	filter := bson.M{"user_id": userID}
	pagination := orm.Pagination{
		SortField: "created_at",
		SortOrder: -1, // Newest first
	}

	result, err := r.ormRepo.FindAll(ctx, filter, pagination)
	if err != nil {
		r.logger.Error("Failed to get boards by user ID", map[string]interface{}{
			"user_id": userID,
			"error":   err.Error(),
		})
		return nil, err
	}

	return result.Items, nil
}

func (r *boardRepository) GetDefaultBoard(ctx context.Context, userID string) (*Board, error) {
	filter := bson.M{
		"user_id":    userID,
		"is_default": true,
	}
	pagination := orm.Pagination{Limit: 1}

	result, err := r.ormRepo.FindAll(ctx, filter, pagination)
	if err != nil {
		r.logger.Error("Failed to get default board", map[string]interface{}{
			"user_id": userID,
			"error":   err.Error(),
		})
		return nil, err
	}

	if len(result.Items) == 0 {
		return nil, orm.ErrDocumentNotFound
	}

	return &result.Items[0], nil
}

func (r *boardRepository) UpdateBoard(ctx context.Context, board *Board) error {
	board.UpdatedAt = time.Now().Unix()

	err := r.ormRepo.Update(ctx, board)
	if err != nil {
		r.logger.Error("Failed to update board", map[string]interface{}{
			"board_id": board.ID,
			"error":    err.Error(),
		})
		return err
	}

	r.logger.Info("Board updated successfully", map[string]interface{}{
		"board_id": board.ID,
		"name":     board.Name,
	})

	return nil
}

func (r *boardRepository) DeleteBoard(ctx context.Context, id string) error {
	err := r.ormRepo.Delete(ctx, id)
	if err != nil {
		r.logger.Error("Failed to delete board", map[string]interface{}{
			"board_id": id,
			"error":    err.Error(),
		})
		return err
	}

	r.logger.Info("Board deleted successfully", map[string]interface{}{
		"board_id": id,
	})

	return nil
}

// ColumnRepository implementation

func (r *columnRepository) CreateColumn(ctx context.Context, column *Column) error {
	now := time.Now().Unix()
	column.SetCreatedAt(now)

	err := r.ormRepo.Create(ctx, column)
	if err != nil {
		r.logger.Error("Failed to create column", map[string]interface{}{
			"error":    err.Error(),
			"name":     column.Name,
			"board_id": column.BoardID,
		})
		return err
	}

	r.logger.Info("Column created successfully", map[string]interface{}{
		"column_id": column.ID,
		"name":      column.Name,
		"board_id":  column.BoardID,
	})

	return nil
}

func (r *columnRepository) GetColumnByID(ctx context.Context, id string) (*Column, error) {
	column, err := r.ormRepo.FindByID(ctx, id)
	if err != nil {
		r.logger.Error("Failed to get column by ID", map[string]interface{}{
			"column_id": id,
			"error":     err.Error(),
		})
		return nil, err
	}

	return column, nil
}

func (r *columnRepository) GetColumnsByBoardID(ctx context.Context, boardID string) ([]Column, error) {
	filter := bson.M{"board_id": boardID}
	pagination := orm.Pagination{
		SortField: "order",
		SortOrder: 1, // Ascending order
	}

	result, err := r.ormRepo.FindAll(ctx, filter, pagination)
	if err != nil {
		r.logger.Error("Failed to get columns by board ID", map[string]interface{}{
			"board_id": boardID,
			"error":    err.Error(),
		})
		return nil, err
	}

	return result.Items, nil
}

func (r *columnRepository) UpdateColumn(ctx context.Context, column *Column) error {
	column.UpdatedAt = time.Now().Unix()

	err := r.ormRepo.Update(ctx, column)
	if err != nil {
		r.logger.Error("Failed to update column", map[string]interface{}{
			"column_id": column.ID,
			"error":     err.Error(),
		})
		return err
	}

	r.logger.Info("Column updated successfully", map[string]interface{}{
		"column_id": column.ID,
		"name":      column.Name,
	})

	return nil
}

func (r *columnRepository) DeleteColumn(ctx context.Context, id string) error {
	err := r.ormRepo.Delete(ctx, id)
	if err != nil {
		r.logger.Error("Failed to delete column", map[string]interface{}{
			"column_id": id,
			"error":     err.Error(),
		})
		return err
	}

	r.logger.Info("Column deleted successfully", map[string]interface{}{
		"column_id": id,
	})

	return nil
}

func (r *columnRepository) UpdateColumnOrder(ctx context.Context, boardID string, columnOrders map[string]int) error {
	// Update each column's order
	for columnID, order := range columnOrders {
		column, err := r.ormRepo.FindByID(ctx, columnID)
		if err != nil {
			r.logger.Error("Failed to find column for order update", map[string]interface{}{
				"column_id": columnID,
				"error":     err.Error(),
			})
			return err
		}

		column.Order = order
		column.UpdatedAt = time.Now().Unix()

		err = r.ormRepo.Update(ctx, column)
		if err != nil {
			r.logger.Error("Failed to update column order", map[string]interface{}{
				"column_id": columnID,
				"order":     order,
				"error":     err.Error(),
			})
			return err
		}
	}

	r.logger.Info("Column orders updated successfully", map[string]interface{}{
		"board_id": boardID,
		"count":    len(columnOrders),
	})

	return nil
}

// CardRepository implementation

func (r *cardRepository) CreateCard(ctx context.Context, card *Card) error {
	now := time.Now().Unix()
	card.SetCreatedAt(now)

	err := r.ormRepo.Create(ctx, card)
	if err != nil {
		r.logger.Error("Failed to create card", map[string]interface{}{
			"error":     err.Error(),
			"title":     card.Title,
			"tender_id": card.TenderID,
			"column_id": card.ColumnID,
		})
		return err
	}

	r.logger.Info("Card created successfully", map[string]interface{}{
		"card_id":   card.ID,
		"title":     card.Title,
		"tender_id": card.TenderID,
		"column_id": card.ColumnID,
	})

	return nil
}

func (r *cardRepository) GetCardByID(ctx context.Context, id string) (*Card, error) {
	card, err := r.ormRepo.FindByID(ctx, id)
	if err != nil {
		r.logger.Error("Failed to get card by ID", map[string]interface{}{
			"card_id": id,
			"error":   err.Error(),
		})
		return nil, err
	}

	return card, nil
}

func (r *cardRepository) GetCardsByColumnID(ctx context.Context, columnID string) ([]Card, error) {
	filter := bson.M{"column_id": columnID}
	pagination := orm.Pagination{
		SortField: "order",
		SortOrder: 1, // Ascending order
	}

	result, err := r.ormRepo.FindAll(ctx, filter, pagination)
	if err != nil {
		r.logger.Error("Failed to get cards by column ID", map[string]interface{}{
			"column_id": columnID,
			"error":     err.Error(),
		})
		return nil, err
	}

	return result.Items, nil
}

func (r *cardRepository) GetCardsByTenderID(ctx context.Context, tenderID string) ([]Card, error) {
	filter := bson.M{"tender_id": tenderID}
	pagination := orm.Pagination{
		SortField: "created_at",
		SortOrder: -1, // Newest first
	}

	result, err := r.ormRepo.FindAll(ctx, filter, pagination)
	if err != nil {
		r.logger.Error("Failed to get cards by tender ID", map[string]interface{}{
			"tender_id": tenderID,
			"error":     err.Error(),
		})
		return nil, err
	}

	return result.Items, nil
}

func (r *cardRepository) UpdateCard(ctx context.Context, card *Card) error {
	card.UpdatedAt = time.Now().Unix()

	err := r.ormRepo.Update(ctx, card)
	if err != nil {
		r.logger.Error("Failed to update card", map[string]interface{}{
			"card_id": card.ID,
			"error":   err.Error(),
		})
		return err
	}

	r.logger.Info("Card updated successfully", map[string]interface{}{
		"card_id": card.ID,
		"title":   card.Title,
	})

	return nil
}

func (r *cardRepository) DeleteCard(ctx context.Context, id string) error {
	err := r.ormRepo.Delete(ctx, id)
	if err != nil {
		r.logger.Error("Failed to delete card", map[string]interface{}{
			"card_id": id,
			"error":   err.Error(),
		})
		return err
	}

	r.logger.Info("Card deleted successfully", map[string]interface{}{
		"card_id": id,
	})

	return nil
}

func (r *cardRepository) MoveCard(ctx context.Context, cardID, targetColumnID string, newOrder int) error {
	card, err := r.ormRepo.FindByID(ctx, cardID)
	if err != nil {
		r.logger.Error("Failed to find card for move", map[string]interface{}{
			"card_id": cardID,
			"error":   err.Error(),
		})
		return err
	}

	oldColumnID := card.ColumnID
	oldOrder := card.Order

	// Update card position
	card.MoveToColumn(targetColumnID, newOrder)

	err = r.ormRepo.Update(ctx, card)
	if err != nil {
		r.logger.Error("Failed to move card", map[string]interface{}{
			"card_id":          cardID,
			"target_column_id": targetColumnID,
			"new_order":        newOrder,
			"error":            err.Error(),
		})
		return err
	}

	r.logger.Info("Card moved successfully", map[string]interface{}{
		"card_id":          cardID,
		"old_column_id":    oldColumnID,
		"target_column_id": targetColumnID,
		"old_order":        oldOrder,
		"new_order":        newOrder,
	})

	return nil
}

func (r *cardRepository) UpdateCardOrder(ctx context.Context, columnID string, cardOrders map[string]int) error {
	// Update each card's order
	for cardID, order := range cardOrders {
		// Find the card
		card, err := r.ormRepo.FindByID(ctx, cardID)
		if err != nil {
			r.logger.Error("Failed to find card for order update", map[string]interface{}{
				"card_id": cardID,
				"error":   err.Error(),
			})
			return err
		}

		// Update the order
		card.Order = order
		card.UpdatedAt = time.Now().Unix()

		// Save the card
		err = r.ormRepo.Update(ctx, card)
		if err != nil {
			r.logger.Error("Failed to update card order", map[string]interface{}{
				"card_id":   cardID,
				"order":     order,
				"column_id": columnID,
				"error":     err.Error(),
			})
			return err
		}
	}

	r.logger.Info("Card orders updated successfully", map[string]interface{}{
		"column_id": columnID,
		"count":     len(cardOrders),
	})

	return nil
}
