package tender

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

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

// TenderRepository interface defines tender data access methods
type TenderRepository interface {
	// Tender CRUD operations
	Create(ctx context.Context, tender *Tender) error
	GetByID(ctx context.Context, id string) (*Tender, error)
	GetByContractNoticeID(ctx context.Context, contractNoticeID string) (*Tender, error)
	GetByNoticePublicationID(ctx context.Context, noticePublicationID string) (*Tender, error)
	GetTenderCountByCountry(ctx context.Context) (map[string]int64, error)
	GetTenderCountByType(ctx context.Context) (map[string]int64, error)
	GetTenderCountByClassification(ctx context.Context) (map[string]int64, error)
	Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Tender, int64, error)
	GetExpiredTenders(ctx context.Context, beforeDate int64) ([]Tender, error)
	Update(ctx context.Context, tender *Tender) error
	Delete(ctx context.Context, id string) error
	GetStatistics(ctx context.Context) (*TenderStatistics, error)
}

func collectionName() string {
	return "tenders"
}

// tenderRepository implements TenderRepository interface using MongoDB ORM
type tenderRepository struct {
	ormRepo      orm.Repository[Tender]
	mongoManager *orm.ConnectionManager
	logger       logger.Logger
}

// NewTenderRepository creates a new tender repository
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) TenderRepository {
	// Create indexes for tenders collection
	tenderIndexes := []orm.Index{
		*orm.CreateUniqueIndex("notice_publication_id_idx", bson.D{{Key: "notice_publication_id", Value: 1}}),
		*orm.NewIndex("contract_notice_id_idx", bson.D{{Key: "contract_notice_id", Value: 1}}),
		*orm.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
		*orm.NewIndex("source_idx", bson.D{{Key: "source", Value: 1}}),
		*orm.NewIndex("country_code_idx", bson.D{{Key: "country_code", Value: 1}}),
		*orm.NewIndex("notice_type_code_idx", bson.D{{Key: "notice_type_code", Value: 1}}),
		*orm.NewIndex("procurement_type_code_idx", bson.D{{Key: "procurement_type_code", Value: 1}}),
		*orm.NewIndex("main_classification_idx", bson.D{{Key: "main_classification", Value: 1}}),
		*orm.NewIndex("publication_date_idx", bson.D{{Key: "publication_date", Value: -1}}),
		*orm.NewIndex("tender_deadline_idx", bson.D{{Key: "tender_deadline", Value: 1}}),
		*orm.NewIndex("estimated_value_idx", bson.D{{Key: "estimated_value", Value: -1}}),
		*orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
		*orm.NewIndex("updated_at_idx", bson.D{{Key: "updated_at", Value: -1}}),
		// Compound indexes for common queries
		*orm.NewIndex("status_country_idx", bson.D{
			{Key: "status", Value: 1},
			{Key: "country_code", Value: 1},
		}),
		*orm.NewIndex("notice_type_status_idx", bson.D{
			{Key: "notice_type_code", Value: 1},
			{Key: "status", Value: 1},
		}),
		// Index for CPV matching queries
		*orm.NewIndex("cpv_matching_idx", bson.D{
			{Key: "status", Value: 1},
			{Key: "main_classification", Value: 1},
			{Key: "additional_classifications", Value: 1},
			{Key: "publication_date", Value: -1},
		}),
		// Text index for search
		*orm.CreateTextIndex("search_idx", "title", "description"),
	}

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

	// Create ORM repositories
	tenderOrmRepo := orm.NewRepository[Tender](mongoManager.GetCollection(collectionName()), logger)

	return &tenderRepository{
		ormRepo:      tenderOrmRepo,
		mongoManager: mongoManager,
		logger:       logger,
	}
}

// Create creates a new tender
func (r *tenderRepository) Create(ctx context.Context, tender *Tender) error {
	now := time.Now().Unix()
	tender.SetCreatedAt(now)

	err := r.ormRepo.Create(ctx, tender)
	if err != nil {
		r.logger.Error("Failed to create tender", map[string]interface{}{
			"error":                 err.Error(),
			"notice_publication_id": tender.NoticePublicationID,
		})
		return err
	}

	r.logger.Info("Tender created successfully", map[string]interface{}{
		"tender_id":             tender.ID,
		"notice_publication_id": tender.NoticePublicationID,
	})

	return nil
}

// GetByID retrieves a tender by ID
func (r *tenderRepository) GetByID(ctx context.Context, id string) (*Tender, error) {
	tender, err := r.ormRepo.FindByID(ctx, id)
	if err != nil {
		r.logger.Error("Failed to get tender by ID", map[string]interface{}{
			"tender_id": id,
			"error":     err.Error(),
		})
		return nil, err
	}

	return tender, nil
}

// GetByContractNoticeID retrieves a tender by contract notice ID
func (r *tenderRepository) GetByContractNoticeID(ctx context.Context, contractNoticeID string) (*Tender, error) {
	filter := bson.M{"contract_notice_id": contractNoticeID}
	pagination := orm.Pagination{Limit: 1}

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

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

	return &result.Items[0], nil
}

// GetByNoticePublicationID retrieves a tender by notice publication ID
func (r *tenderRepository) GetByNoticePublicationID(ctx context.Context, noticePublicationID string) (*Tender, error) {
	filter := bson.M{"notice_publication_id": noticePublicationID}
	pagination := orm.Pagination{Limit: 1}

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

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

	return &result.Items[0], nil
}

// Update updates an existing tender
func (r *tenderRepository) Update(ctx context.Context, tender *Tender) error {
	tender.UpdatedAt = time.Now().Unix()

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

	r.logger.Info("Tender updated successfully", map[string]interface{}{
		"tender_id": tender.ID,
	})

	return nil
}

// Delete deletes a tender by ID
func (r *tenderRepository) Delete(ctx context.Context, id string) error {
	err := r.ormRepo.Delete(ctx, id)
	if err != nil {
		r.logger.Error("Failed to delete tender", map[string]interface{}{
			"tender_id": id,
			"error":     err.Error(),
		})
		return err
	}

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

	return nil
}

// Search searches tenders based on criteria
func (r *tenderRepository) Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Tender, int64, error) {
	filter := r.buildSearchFilter(form)

	// Build sort
	sort := "created_at"
	if pagination.SortBy != "" {
		sort = pagination.SortBy
	}

	sortOrder := "desc"
	if pagination.SortOrder != "" {
		sortOrder = pagination.SortOrder
	}

	// Build pagination
	paginationBuilder := orm.NewPaginationBuilder().
		Limit(pagination.Limit).
		Skip(pagination.Offset).
		SortBy(sort, sortOrder).
		Build()

	result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
	if err != nil {
		r.logger.Error("Failed to search tenders", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, 0, err
	}

	return result.Items, result.TotalCount, nil
}

// GetExpiredTenders retrieves tenders that are expired
func (r *tenderRepository) GetExpiredTenders(ctx context.Context, beforeDate int64) ([]Tender, error) {
	filter := bson.M{
		"$or": []bson.M{
			{"status": TenderStatusExpired},
			{
				"tender_deadline": bson.M{
					"$lt": beforeDate,
				},
				"status": bson.M{"$ne": TenderStatusExpired},
			},
		},
	}

	pagination := orm.Pagination{
		Limit:     1000, // Large limit for maintenance operations
		SortField: "tender_deadline",
		SortOrder: 1,
	}

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

	return result.Items, nil
}

// GetStatistics calculates tender statistics using aggregation
func (r *tenderRepository) GetStatistics(ctx context.Context) (*TenderStatistics, error) {
	stats := &TenderStatistics{
		LastUpdated: time.Now().Unix(),
	}

	// Get total count
	total, err := r.ormRepo.Count(ctx, bson.M{})
	if err != nil {
		return nil, err
	}
	stats.TotalTenders = total

	// Get active count
	active, err := r.ormRepo.Count(ctx, bson.M{"status": TenderStatusActive})
	if err != nil {
		return nil, err
	}
	stats.ActiveTenders = active

	// Get expired count
	expired, err := r.ormRepo.Count(ctx, bson.M{"status": TenderStatusExpired})
	if err != nil {
		return nil, err
	}
	stats.ExpiredTenders = expired

	// Get statistics by country, type, and classification
	stats.TendersByCountry, _ = r.GetTenderCountByCountry(ctx)
	stats.TendersByType, _ = r.GetTenderCountByType(ctx)
	stats.TendersByClassification, _ = r.GetTenderCountByClassification(ctx)

	// Calculate value statistics using aggregation
	pipeline := mongo.Pipeline{
		{
			{Key: "$group", Value: bson.M{
				"_id":                   nil,
				"total_estimated_value": bson.M{"$sum": "$estimated_value"},
				"count":                 bson.M{"$sum": 1},
			}},
		},
	}

	result, err := r.ormRepo.Aggregate(ctx, pipeline)
	if err == nil && len(result) > 0 {
		if totalValue, ok := result[0]["total_estimated_value"].(float64); ok {
			stats.TotalEstimatedValue = totalValue
			if count, ok := result[0]["count"].(int32); ok && count > 0 {
				stats.AverageEstimatedValue = totalValue / float64(count)
			}
		}
	}

	return stats, nil
}

// GetTenderCountByCountry gets tender count grouped by country
func (r *tenderRepository) GetTenderCountByCountry(ctx context.Context) (map[string]int64, error) {
	pipeline := mongo.Pipeline{
		{
			{Key: "$group", Value: bson.M{
				"_id":   "$country_code",
				"count": bson.M{"$sum": 1},
			}},
		},
	}

	results, err := r.ormRepo.Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}

	counts := make(map[string]int64)
	for _, result := range results {
		if country, ok := result["_id"].(string); ok {
			if count, ok := result["count"].(int32); ok {
				counts[country] = int64(count)
			}
		}
	}

	return counts, nil
}

// GetTenderCountByType gets tender count grouped by type
func (r *tenderRepository) GetTenderCountByType(ctx context.Context) (map[string]int64, error) {
	pipeline := mongo.Pipeline{
		{
			{Key: "$group", Value: bson.M{
				"_id":   "$notice_type_code",
				"count": bson.M{"$sum": 1},
			}},
		},
	}

	results, err := r.ormRepo.Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}

	counts := make(map[string]int64)
	for _, result := range results {
		if noticeType, ok := result["_id"].(string); ok {
			if count, ok := result["count"].(int32); ok {
				counts[noticeType] = int64(count)
			}
		}
	}

	return counts, nil
}

// GetTenderCountByClassification gets tender count grouped by classification
func (r *tenderRepository) GetTenderCountByClassification(ctx context.Context) (map[string]int64, error) {
	pipeline := mongo.Pipeline{
		{
			{Key: "$group", Value: bson.M{
				"_id":   "$main_classification",
				"count": bson.M{"$sum": 1},
			}},
		},
		{
			{Key: "$limit", Value: 20},
		},
	}

	results, err := r.ormRepo.Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}

	counts := make(map[string]int64)
	for _, result := range results {
		if classification, ok := result["_id"].(string); ok {
			if count, ok := result["count"].(int32); ok {
				counts[classification] = int64(count)
			}
		}
	}

	return counts, nil
}

// buildSearchFilter builds MongoDB filter from search form
func (r *tenderRepository) buildSearchFilter(form *SearchForm) bson.M {
	filter := bson.M{}

	if form.Search != nil {
		filter["$text"] = bson.M{"$search": *form.Search}
	}

	if form.NoticeType != nil {
		filter["notice_type_code"] = form.NoticeType
	}

	if form.ProcurementType != nil {
		filter["procurement_type_code"] = form.ProcurementType
	}

	if len(form.CountryCodes) > 0 {
		filter["country_code"] = bson.M{"$in": form.CountryCodes}
	}

	if len(form.RegionCodes) > 0 {
		filter["region_code"] = bson.M{"$in": form.RegionCodes}
	}

	if len(form.Classifications) > 0 {
		filter["$or"] = []bson.M{
			{"main_classification": bson.M{"$in": form.Classifications}},
			{"additional_classifications": bson.M{"$in": form.Classifications}},
		}
	}

	if form.MinEstimatedValue != nil || form.MaxEstimatedValue != nil {
		valueFilter := bson.M{}
		if form.MinEstimatedValue != nil {
			valueFilter["$gte"] = *form.MinEstimatedValue
		}
		if form.MaxEstimatedValue != nil {
			valueFilter["$lte"] = *form.MaxEstimatedValue
		}
		filter["estimated_value"] = valueFilter
	}

	if form.Currency != "" {
		filter["currency"] = form.Currency
	}

	if form.DeadlineFrom != nil || form.DeadlineTo != nil {
		deadlineFilter := bson.M{}
		if form.DeadlineFrom != nil {
			deadlineFilter["$gte"] = *form.DeadlineFrom
		}
		if form.DeadlineTo != nil {
			deadlineFilter["$lte"] = *form.DeadlineTo
		}
		filter["tender_deadline"] = deadlineFilter
	}

	if form.PublicationDateFrom != nil || form.PublicationDateTo != nil {
		pubDateFilter := bson.M{}
		if form.PublicationDateFrom != nil {
			pubDateFilter["$gte"] = *form.PublicationDateFrom
		}
		if form.PublicationDateTo != nil {
			pubDateFilter["$lte"] = *form.PublicationDateTo
		}
		filter["publication_date"] = pubDateFilter
	}

	if len(form.Status) > 0 {
		filter["status"] = bson.M{"$in": form.Status}
	}

	if len(form.Source) > 0 {
		filter["source"] = bson.M{"$in": form.Source}
	}

	if form.BuyerOrganizationID != nil {
		filter["buyer_organization.id"] = *form.BuyerOrganizationID
	}

	if len(form.Languages) > 0 {
		filter["notice_language_code"] = bson.M{"$in": form.Languages}
	}

	return filter
}
