package tender

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

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/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)
	Update(ctx context.Context, tender *Tender) error
	Delete(ctx context.Context, id string) error
	List(ctx context.Context, criteria TenderSearchCriteria, limit, offset int, from *int64, to *int64) ([]Tender, int64, error)

	// Bulk operations
	CreateBatch(ctx context.Context, tenders []Tender) error
	UpdateBatch(ctx context.Context, tenders []Tender) error

	// Search and filtering
	Search(ctx context.Context, criteria TenderSearchCriteria) ([]Tender, error)
	GetByCountryCode(ctx context.Context, countryCode string, limit, offset int) ([]Tender, error)
	GetByStatus(ctx context.Context, status TenderStatus, limit, offset int) ([]Tender, error)
	GetExpiredTenders(ctx context.Context, beforeDate int64) ([]Tender, error)
	GetActiveTenders(ctx context.Context, limit, offset int) ([]Tender, error)

	// Statistics and aggregations
	GetStatistics(ctx context.Context) (*TenderStatistics, 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)
}

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

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

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

	// Create ORM repositories
	tenderOrmRepo := mongopkg.NewRepository[Tender](mongoManager.GetCollection("tenders"), logger)

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

// Create creates a new tender
func (r *tenderRepository) Create(ctx context.Context, tender *Tender) error {
	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 := mongopkg.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, mongopkg.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 := mongopkg.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, mongopkg.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
}

// List retrieves tenders with pagination and filtering
func (r *tenderRepository) List(ctx context.Context, criteria TenderSearchCriteria, limit, offset int, from *int64, to *int64) ([]Tender, int64, error) {
	filter := r.buildSearchFilter(criteria)

	pagination := mongopkg.Pagination{
		Limit:     limit,
		Skip:      offset,
		SortField: "publication_date",
		SortOrder: -1,
	}

	if from != nil {
		filter["publication_date"] = bson.M{"$gte": *from}
	}

	if to != nil {
		filter["publication_date"] = bson.M{"$lte": *to}
	}

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

	return result.Items, result.TotalCount, nil
}

// CreateBatch creates multiple tenders in a single operation
func (r *tenderRepository) CreateBatch(ctx context.Context, tenders []Tender) error {
	if len(tenders) == 0 {
		return nil
	}

	for i := range tenders {
		if err := r.ormRepo.Create(ctx, &tenders[i]); err != nil {
			r.logger.Error("Failed to create tender in batch", map[string]interface{}{
				"tender_id": tenders[i].ID,
				"error":     err.Error(),
			})
			return err
		}
	}

	r.logger.Info("Tender batch created successfully", map[string]interface{}{
		"count": len(tenders),
	})

	return nil
}

// UpdateBatch updates multiple tenders
func (r *tenderRepository) UpdateBatch(ctx context.Context, tenders []Tender) error {
	if len(tenders) == 0 {
		return nil
	}

	now := time.Now().Unix()

	for i := range tenders {
		tenders[i].UpdatedAt = now

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

	r.logger.Info("Tender batch updated successfully", map[string]interface{}{
		"count": len(tenders),
	})

	return nil
}

// Search searches tenders based on criteria
func (r *tenderRepository) Search(ctx context.Context, criteria TenderSearchCriteria) ([]Tender, error) {
	filter := r.buildSearchFilter(criteria)

	pagination := mongopkg.Pagination{
		Limit:     100,
		SortField: "created_at",
		SortOrder: -1,
	}

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

	return result.Items, nil
}

// GetByCountryCode retrieves tenders by country code
func (r *tenderRepository) GetByCountryCode(ctx context.Context, countryCode string, limit, offset int) ([]Tender, error) {
	filter := bson.M{"country_code": countryCode}

	pagination := mongopkg.Pagination{
		Limit:     limit,
		Skip:      offset,
		SortField: "created_at",
		SortOrder: -1,
	}

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

	return result.Items, nil
}

// GetByStatus retrieves tenders by status
func (r *tenderRepository) GetByStatus(ctx context.Context, status TenderStatus, limit, offset int) ([]Tender, error) {
	filter := bson.M{"status": status}

	pagination := mongopkg.Pagination{
		Limit:     limit,
		Skip:      offset,
		SortField: "created_at",
		SortOrder: -1,
	}

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

	return result.Items, 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 := mongopkg.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
}

// GetActiveTenders retrieves active tenders
func (r *tenderRepository) GetActiveTenders(ctx context.Context, limit, offset int) ([]Tender, error) {
	filter := bson.M{"status": TenderStatusActive}

	pagination := mongopkg.Pagination{
		Limit:     limit,
		Skip:      offset,
		SortField: "tender_deadline",
		SortOrder: 1,
	}

	result, err := r.ormRepo.FindAll(ctx, filter, pagination)
	if err != nil {
		r.logger.Error("Failed to get active tenders", map[string]interface{}{
			"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 criteria
func (r *tenderRepository) buildSearchFilter(criteria TenderSearchCriteria) bson.M {
	filter := bson.M{}

	if criteria.Query != "" {
		filter["$text"] = bson.M{"$search": criteria.Query}
	}

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

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

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

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

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

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

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

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

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

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

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

	if criteria.BuyerOrganizationID != "" {
		filter["buyer_organization.id"] = criteria.BuyerOrganizationID
	}

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

	return filter
}
