package tender_approval

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

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

// Repository defines the interface for tender approval data operations
type Repository interface {
	Create(ctx context.Context, tenderApproval *TenderApproval) error
	GetByID(ctx context.Context, id string) (*TenderApproval, error)
	GetByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error)
	GetByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error)
	GetByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, limit int, offset int, from *int64, to *int64) ([]*TenderApproval, int64, error)
	GetByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error)
	Update(ctx context.Context, tenderApproval *TenderApproval) error
	Delete(ctx context.Context, id string) error
	List(ctx context.Context, limit, offset int) ([]*TenderApproval, error)
	Search(ctx context.Context, tenderID *string, companyID *string, status []ApprovalStatus, submissionMode []SubmissionMode, createdAtFrom *int64, createdAtTo *int64, limit, offset int, sortBy, sortOrder string) ([]*TenderApproval, error)
	Count(ctx context.Context) (int64, error)
	CountByStatus(ctx context.Context, status ApprovalStatus) (int64, error)
	CountBySubmissionMode(ctx context.Context, submissionMode SubmissionMode) (int64, error)
	CountByTenderID(ctx context.Context, tenderID string) (int64, error)
	CountByCompanyID(ctx context.Context, companyID string) (int64, error)
	GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, error)
	GetCompanyTenderApprovalStats(ctx context.Context, companyID string) (*CompanyTenderApprovalStatsResponse, error)
	SearchByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error)
}

// tenderApprovalRepository implements the Repository interface using the MongoDB ORM
type tenderApprovalRepository struct {
	ormRepo mongo.Repository[TenderApproval]
	logger  logger.Logger
}

// NewTenderApprovalRepository creates a new tender approval repository
func NewTenderApprovalRepository(mongoManager *mongo.ConnectionManager, logger logger.Logger) Repository {
	// Create indexes using the ORM's index management
	indexes := []mongo.Index{
		*mongo.CreateUniqueIndex("tender_company_idx", bson.D{{Key: "tender_id", Value: 1}, {Key: "company_id", Value: 1}}),
		*mongo.NewIndex("tender_id_idx", bson.D{{Key: "tender_id", Value: 1}}),
		*mongo.NewIndex("company_id_idx", bson.D{{Key: "company_id", Value: 1}}),
		*mongo.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
		*mongo.NewIndex("submission_mode_idx", bson.D{{Key: "submission_mode", Value: 1}}),
		*mongo.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
		*mongo.NewIndex("updated_at_idx", bson.D{{Key: "updated_at", Value: -1}}),
	}

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

	// Create ORM repository
	ormRepo := mongo.NewRepository[TenderApproval](mongoManager.GetCollection("tender_approvals"), logger)

	return &tenderApprovalRepository{
		ormRepo: ormRepo,
		logger:  logger,
	}
}

// Create creates a new tender approval
func (r *tenderApprovalRepository) Create(ctx context.Context, tenderApproval *TenderApproval) error {
	// Set created/updated timestamps using Unix timestamps
	now := time.Now().Unix()
	tenderApproval.SetCreatedAt(now)
	tenderApproval.SetUpdatedAt(now)

	// Use ORM to create tender approval
	err := r.ormRepo.Create(ctx, tenderApproval)
	if err != nil {
		r.logger.Error("Failed to create tender approval", map[string]interface{}{
			"error":      err.Error(),
			"tender_id":  tenderApproval.TenderID,
			"company_id": tenderApproval.CompanyID,
		})
		return err
	}

	r.logger.Info("Tender approval created successfully", map[string]interface{}{
		"tender_approval_id": tenderApproval.ID,
		"tender_id":          tenderApproval.TenderID,
		"company_id":         tenderApproval.CompanyID,
		"status":             tenderApproval.Status,
	})

	return nil
}

// GetByID retrieves a tender approval by ID
func (r *tenderApprovalRepository) GetByID(ctx context.Context, id string) (*TenderApproval, error) {
	tenderApproval, err := r.ormRepo.FindByID(ctx, id)
	if err != nil {
		if errors.Is(err, mongo.ErrDocumentNotFound) {
			return nil, errors.New("tender approval not found")
		}
		r.logger.Error("Failed to get tender approval by ID", map[string]interface{}{
			"error":              err.Error(),
			"tender_approval_id": id,
		})
		return nil, err
	}

	return tenderApproval, nil
}

// GetByTenderAndCompany retrieves a tender approval by tender ID and company ID
func (r *tenderApprovalRepository) GetByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error) {
	filter := bson.M{
		"tender_id":  tenderID,
		"company_id": companyID,
	}

	tenderApproval, err := r.ormRepo.FindOne(ctx, filter)
	if err != nil {
		if errors.Is(err, mongo.ErrDocumentNotFound) {
			return nil, errors.New("tender approval not found")
		}
		r.logger.Error("Failed to get tender approval by tender and company", map[string]interface{}{
			"error":      err.Error(),
			"tender_id":  tenderID,
			"company_id": companyID,
		})
		return nil, err
	}

	return tenderApproval, nil
}

// GetByTenderID retrieves all tender approvals for a specific tender
func (r *tenderApprovalRepository) GetByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error) {
	filter := bson.M{"tender_id": tenderID}

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

	// Convert []TenderApproval to []*TenderApproval
	tenderApprovals := make([]*TenderApproval, len(result.Items))
	for i := range result.Items {
		tenderApprovals[i] = &result.Items[i]
	}

	return tenderApprovals, nil
}

// GetByCompanyID retrieves all tender approvals for a specific company with optional filters
func (r *tenderApprovalRepository) GetByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, limit int, offset int, from *int64, to *int64) ([]*TenderApproval, int64, error) {
	filter := bson.M{"company_id": companyID}

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

	// Add submission mode filter if provided
	if submissionMode != nil {
		filter["submission_mode"] = *submissionMode
	}

	// Add status filter if provided
	if status != nil {
		filter["status"] = *status
	}

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

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

	// Convert []TenderApproval to []*TenderApproval
	tenderApprovals := make([]*TenderApproval, len(result.Items))
	for i := range result.Items {
		tenderApprovals[i] = &result.Items[i]
	}

	return tenderApprovals, result.TotalCount, nil
}

// GetByIDs retrieves multiple tender approvals by their IDs
func (r *tenderApprovalRepository) GetByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) {
	if len(ids) == 0 {
		return []*TenderApproval{}, nil
	}

	// Convert string IDs to ObjectIDs for MongoDB query
	objectIDs := make([]interface{}, len(ids))
	for i, id := range ids {
		objectID, err := primitive.ObjectIDFromHex(id)
		if err != nil {
			r.logger.Warn("Invalid tender approval ID format", map[string]interface{}{
				"id":    id,
				"error": err.Error(),
			})
			continue
		}
		objectIDs[i] = objectID
	}

	if len(objectIDs) == 0 {
		return []*TenderApproval{}, nil
	}

	filter := bson.M{"_id": bson.M{"$in": objectIDs}}
	result, err := r.ormRepo.FindAll(ctx, filter, mongo.NewPaginationBuilder().Limit(len(ids)).Build())
	if err != nil {
		r.logger.Error("Failed to get tender approvals by IDs", map[string]interface{}{
			"error": err.Error(),
			"ids":   ids,
		})
		return nil, err
	}

	// Convert []TenderApproval to []*TenderApproval
	tenderApprovals := make([]*TenderApproval, len(result.Items))
	for i := range result.Items {
		tenderApprovals[i] = &result.Items[i]
	}

	r.logger.Info("Retrieved tender approvals by IDs", map[string]interface{}{
		"requested_count": len(ids),
		"found_count":     len(tenderApprovals),
	})

	return tenderApprovals, nil
}

// Update updates a tender approval
func (r *tenderApprovalRepository) Update(ctx context.Context, tenderApproval *TenderApproval) error {
	// Set updated timestamp using Unix timestamp
	tenderApproval.SetUpdatedAt(time.Now().Unix())

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

	r.logger.Info("Tender approval updated successfully", map[string]interface{}{
		"tender_approval_id": tenderApproval.ID,
		"tender_id":          tenderApproval.TenderID,
		"company_id":         tenderApproval.CompanyID,
	})

	return nil
}

// Delete deletes a tender approval
func (r *tenderApprovalRepository) Delete(ctx context.Context, id string) error {
	// Get tender approval first
	tenderApproval, err := r.GetByID(ctx, id)
	if err != nil {
		return err
	}

	// Delete from database
	err = r.ormRepo.Delete(ctx, id)
	if err != nil {
		r.logger.Error("Failed to delete tender approval", map[string]interface{}{
			"error":              err.Error(),
			"tender_approval_id": id,
		})
		return err
	}

	r.logger.Info("Tender approval deleted successfully", map[string]interface{}{
		"tender_approval_id": id,
		"tender_id":          tenderApproval.TenderID,
		"company_id":         tenderApproval.CompanyID,
	})

	return nil
}

// List retrieves tender approvals with pagination
func (r *tenderApprovalRepository) List(ctx context.Context, limit, offset int) ([]*TenderApproval, error) {
	// Build pagination
	pagination := mongo.NewPaginationBuilder().
		Limit(limit).
		Skip(offset).
		SortDesc("created_at").
		Build()

	// Use ORM to find tender approvals
	result, err := r.ormRepo.FindAll(ctx, bson.M{}, pagination)
	if err != nil {
		r.logger.Error("Failed to list tender approvals", map[string]interface{}{
			"error":  err.Error(),
			"limit":  limit,
			"offset": offset,
		})
		return nil, err
	}

	// Convert []TenderApproval to []*TenderApproval
	tenderApprovals := make([]*TenderApproval, len(result.Items))
	for i := range result.Items {
		tenderApprovals[i] = &result.Items[i]
	}

	return tenderApprovals, nil
}

// Search retrieves tender approvals with search and filters
func (r *tenderApprovalRepository) Search(ctx context.Context, tenderID *string, companyID *string, status []ApprovalStatus, submissionMode []SubmissionMode, createdAtFrom *int64, createdAtTo *int64, limit, offset int, sortBy, sortOrder string) ([]*TenderApproval, error) {
	// Build filter
	filter := bson.M{}

	if tenderID != nil {
		filter["tender_id"] = *tenderID
	}

	if companyID != nil {
		filter["company_id"] = *companyID
	}

	if len(status) > 0 {
		statusStrings := make([]string, len(status))
		for i, s := range status {
			statusStrings[i] = string(s)
		}
		filter["status"] = bson.M{"$in": statusStrings}
	}

	if len(submissionMode) > 0 {
		modeStrings := make([]string, len(submissionMode))
		for i, m := range submissionMode {
			modeStrings[i] = string(m)
		}
		filter["submission_mode"] = bson.M{"$in": modeStrings}
	}

	// Date range filters
	if createdAtFrom != nil || createdAtTo != nil {
		dateFilter := bson.M{}
		if createdAtFrom != nil {
			dateFilter["$gte"] = *createdAtFrom
		}
		if createdAtTo != nil {
			dateFilter["$lte"] = *createdAtTo
		}
		filter["created_at"] = dateFilter
	}

	// Build pagination
	pagination := mongo.NewPaginationBuilder().
		Limit(limit).
		Skip(offset)

	// Set sort
	if sortBy != "" {
		if sortOrder == "desc" {
			pagination.SortDesc(sortBy)
		} else {
			pagination.SortAsc(sortBy)
		}
	} else {
		pagination.SortDesc("created_at") // Default sort
	}

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

	// Convert []TenderApproval to []*TenderApproval
	tenderApprovals := make([]*TenderApproval, len(result.Items))
	for i := range result.Items {
		tenderApprovals[i] = &result.Items[i]
	}

	return tenderApprovals, nil
}

// Count counts all tender approvals
func (r *tenderApprovalRepository) Count(ctx context.Context) (int64, error) {
	count, err := r.ormRepo.Count(ctx, bson.M{})
	if err != nil {
		r.logger.Error("Failed to count tender approvals", map[string]interface{}{
			"error": err.Error(),
		})
		return 0, err
	}

	return count, nil
}

// CountByStatus counts tender approvals by status
func (r *tenderApprovalRepository) CountByStatus(ctx context.Context, status ApprovalStatus) (int64, error) {
	filter := bson.M{"status": string(status)}

	count, err := r.ormRepo.Count(ctx, filter)
	if err != nil {
		r.logger.Error("Failed to count tender approvals by status", map[string]interface{}{
			"error":  err.Error(),
			"status": status,
		})
		return 0, err
	}

	return count, nil
}

// CountBySubmissionMode counts tender approvals by submission mode
func (r *tenderApprovalRepository) CountBySubmissionMode(ctx context.Context, submissionMode SubmissionMode) (int64, error) {
	filter := bson.M{"submission_mode": string(submissionMode)}

	count, err := r.ormRepo.Count(ctx, filter)
	if err != nil {
		r.logger.Error("Failed to count tender approvals by submission mode", map[string]interface{}{
			"error":           err.Error(),
			"submission_mode": submissionMode,
		})
		return 0, err
	}

	return count, nil
}

// CountByTenderID counts tender approvals for a specific tender
func (r *tenderApprovalRepository) CountByTenderID(ctx context.Context, tenderID string) (int64, error) {
	filter := bson.M{"tender_id": tenderID}

	count, err := r.ormRepo.Count(ctx, filter)
	if err != nil {
		r.logger.Error("Failed to count tender approvals by tender ID", map[string]interface{}{
			"error":     err.Error(),
			"tender_id": tenderID,
		})
		return 0, err
	}

	return count, nil
}

// CountByCompanyID counts tender approvals for a specific company
func (r *tenderApprovalRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) {
	filter := bson.M{"company_id": companyID}

	count, err := r.ormRepo.Count(ctx, filter)
	if err != nil {
		r.logger.Error("Failed to count tender approvals by company ID", map[string]interface{}{
			"error":      err.Error(),
			"company_id": companyID,
		})
		return 0, err
	}

	return count, nil
}

// GetTenderApprovalStats returns tender approval statistics
func (r *tenderApprovalRepository) GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, error) {
	// Get basic counts
	totalApprovals, _ := r.Count(ctx)
	submittedTenders, _ := r.CountByStatus(ctx, ApprovalStatusSubmitted)
	rejectedTenders, _ := r.CountByStatus(ctx, ApprovalStatusRejected)

	stats := &TenderApprovalStatsResponse{
		TotalApprovals:   totalApprovals,
		SubmittedTenders: submittedTenders,
		RejectedTenders:  rejectedTenders,
		LastUpdated:      time.Now().Unix(),
	}

	return stats, nil
}

// GetCompanyTenderApprovalStats returns company-specific tender approval statistics
func (r *tenderApprovalRepository) GetCompanyTenderApprovalStats(ctx context.Context, companyID string) (*CompanyTenderApprovalStatsResponse, error) {
	// Get company-specific counts
	totalApprovals, _ := r.CountByCompanyID(ctx, companyID)

	// Count by status for this company
	submittedFilter := bson.M{"company_id": companyID, "status": string(ApprovalStatusSubmitted)}
	submittedTenders, _ := r.ormRepo.Count(ctx, submittedFilter)

	rejectedFilter := bson.M{"company_id": companyID, "status": string(ApprovalStatusRejected)}
	rejectedTenders, _ := r.ormRepo.Count(ctx, rejectedFilter)

	// Count by submission mode for this company
	selfApplyFilter := bson.M{"company_id": companyID, "status": string(ApprovalStatusSubmitted), "submission_mode": string(SubmissionModeSelfApply)}
	selfApplyCount, _ := r.ormRepo.Count(ctx, selfApplyFilter)

	partnershipFilter := bson.M{"company_id": companyID, "status": string(ApprovalStatusSubmitted), "submission_mode": string(SubmissionModePartnership)}
	partnershipCount, _ := r.ormRepo.Count(ctx, partnershipFilter)

	stats := &CompanyTenderApprovalStatsResponse{
		CompanyID:        companyID,
		TotalApprovals:   totalApprovals,
		SubmittedTenders: submittedTenders,
		RejectedTenders:  rejectedTenders,
		SelfApplyCount:   selfApplyCount,
		PartnershipCount: partnershipCount,
		LastUpdated:      time.Now().Unix(),
	}

	return stats, nil
}

// SearchByCriteria searches tender approvals using search criteria
func (r *tenderApprovalRepository) SearchByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error) {
	// Convert criteria to search parameters
	var status []ApprovalStatus
	if criteria.Status != nil {
		status = criteria.Status
	}

	var submissionMode []SubmissionMode
	if criteria.SubmissionMode != nil {
		submissionMode = criteria.SubmissionMode
	}

	// Use the search method
	return r.Search(ctx, criteria.TenderID, criteria.CompanyID, status, submissionMode, criteria.CreatedAtFrom, criteria.CreatedAtTo, criteria.Limit, criteria.Offset, "created_at", "desc")
}
