package tender

import (
	"context"
	"fmt"
	"math"
	"time"
	"tm/internal/company"
	"tm/pkg/logger"
	"tm/pkg/response"
)

// TenderService interface defines tender business logic operations
type TenderService interface {
	// Tender operations
	GetTenderByID(ctx context.Context, id string) (*TenderResponse, error)
	UpdateTender(ctx context.Context, req UpdateTenderRequest) (*Tender, error)
	DeleteTender(ctx context.Context, id string) error
	ListTenders(ctx context.Context, req ListTendersRequest) (*ListTendersResponse, error)
	RecommendTenders(ctx context.Context, companyID *string, from *int64, to *int64, limit, offset int) (*ListTendersResponse, error)

	// Maintenance operations
	UpdateExpiredTenders(ctx context.Context) error
	CleanupOldData(ctx context.Context, olderThan time.Duration) error
}

// tenderService implements TenderService interface
type tenderService struct {
	repository     TenderRepository
	companyService company.Service
	logger         logger.Logger
}

// NewTenderService creates a new tender service
func NewTenderService(repository TenderRepository, companyService company.Service, logger logger.Logger) TenderService {
	return &tenderService{
		repository:     repository,
		companyService: companyService,
		logger:         logger,
	}
}

// GetTenderByID retrieves a tender by ID
func (s *tenderService) GetTenderByID(ctx context.Context, id string) (*TenderResponse, error) {
	s.logger.Info("Retrieving tender by ID", map[string]interface{}{
		"tender_id": id,
	})

	tender, err := s.repository.GetByID(ctx, id)
	if err != nil {
		s.logger.Error("Failed to retrieve tender", map[string]interface{}{
			"tender_id": id,
			"error":     err.Error(),
		})
		return nil, fmt.Errorf("failed to retrieve tender: %w", err)
	}

	return tender.ToTenderResponse(), nil
}

// UpdateTender updates an existing tender
func (s *tenderService) UpdateTender(ctx context.Context, req UpdateTenderRequest) (*Tender, error) {
	s.logger.Info("Updating tender", map[string]interface{}{
		"tender_id": req.ID,
	})

	// Get existing tender
	tender, err := s.repository.GetByID(ctx, req.ID)
	if err != nil {
		s.logger.Error("Failed to find tender for update", map[string]interface{}{
			"tender_id": req.ID,
			"error":     err.Error(),
		})
		return nil, fmt.Errorf("failed to find tender: %w", err)
	}

	// Update fields if provided
	if req.Title != nil {
		tender.Title = *req.Title
	}
	if req.Description != nil {
		tender.Description = *req.Description
	}
	if req.Status != nil {
		tender.Status = *req.Status
	}
	if req.EstimatedValue != nil {
		tender.EstimatedValue = *req.EstimatedValue
	}
	if req.Currency != nil {
		tender.Currency = *req.Currency
	}
	if req.TenderDeadline != nil {
		tender.TenderDeadline = *req.TenderDeadline
	}

	if err := s.repository.Update(ctx, tender); err != nil {
		s.logger.Error("Failed to update tender", map[string]interface{}{
			"tender_id": req.ID,
			"error":     err.Error(),
		})
		return nil, fmt.Errorf("failed to update tender: %w", err)
	}

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

	return tender, nil
}

// DeleteTender deletes a tender
func (s *tenderService) DeleteTender(ctx context.Context, id string) error {
	s.logger.Info("Deleting tender", map[string]interface{}{
		"tender_id": id,
	})

	if err := s.repository.Delete(ctx, id); err != nil {
		s.logger.Error("Failed to delete tender", map[string]interface{}{
			"tender_id": id,
			"error":     err.Error(),
		})
		return fmt.Errorf("failed to delete tender: %w", err)
	}

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

	return nil
}

// ListTenders retrieves tenders with pagination and filtering
func (s *tenderService) ListTenders(ctx context.Context, req ListTendersRequest) (*ListTendersResponse, error) {
	s.logger.Info("Listing tenders", map[string]interface{}{
		"limit":      req.Limit,
		"offset":     req.Offset,
		"company_id": req.CompanyID,
		"from":       req.From,
		"to":         req.To,
	})

	if req.Limit <= 0 {
		req.Limit = 20
	}
	if req.Limit > 100 {
		req.Limit = 100
	}

	tenders, total, err := s.repository.List(ctx, req.Criteria, req.Limit, req.Offset, req.From, req.To)
	if err != nil {
		s.logger.Error("Failed to list tenders", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, fmt.Errorf("failed to list tenders: %w", err)
	}

	// If no company ID provided, return tenders without match percentage
	if req.CompanyID == nil || *req.CompanyID == "" {
		tenderResponses := make([]TenderResponse, 0, len(tenders))
		for _, tender := range tenders {
			tenderResponses = append(tenderResponses, *tender.ToTenderResponse())
		}

		return &ListTendersResponse{
			Tenders: tenderResponses,
			Metadata: &response.Meta{
				Total:  int(total),
				Limit:  req.Limit,
				Offset: req.Offset,
			},
		}, nil
	}

	// Get company CPV codes for match calculation
	comp, err := s.companyService.GetCompanyByID(ctx, *req.CompanyID)
	if err != nil {
		s.logger.Error("Failed to get company for match calculation", map[string]interface{}{
			"company_id": *req.CompanyID,
			"error":      err.Error(),
		})
		// Continue without match percentage if company not found
		tenderResponses := make([]TenderResponse, 0, len(tenders))
		for _, tender := range tenders {
			tenderResponses = append(tenderResponses, *tender.ToTenderResponse())
		}

		return &ListTendersResponse{
			Tenders: tenderResponses,
			Metadata: &response.Meta{
				Total:  int(total),
				Limit:  req.Limit,
				Offset: req.Offset,
			},
		}, nil
	}

	var companyCPVCodes []string
	if comp.Tags != nil && comp.Tags.CPVCodes != nil {
		companyCPVCodes = comp.Tags.CPVCodes
	}

	s.logger.Info("Tenders sorted by match percentage", map[string]interface{}{
		"company_id":        *req.CompanyID,
		"company_cpv_codes": len(companyCPVCodes),
		"total_tenders":     len(tenders),
	})

	tenderResponses := make([]TenderResponse, 0, len(tenders))
	for _, tender := range tenders {
		tenderResponses = append(tenderResponses, *tender.ToTenderResponse())
	}

	return &ListTendersResponse{
		Tenders: tenderResponses,
		Metadata: &response.Meta{
			Total:  int(total),
			Limit:  req.Limit,
			Offset: req.Offset,
		},
	}, nil
}

// ListTenders retrieves tenders with pagination and filtering
func (s *tenderService) RecommendTenders(ctx context.Context, companyID *string, from *int64, to *int64, limit, offset int) (*ListTendersResponse, error) {
	s.logger.Info("Recommend tenders", map[string]interface{}{
		"company_id": companyID,
	})

	if limit <= 0 {
		limit = 20
	}
	if limit > 100 {
		limit = 100
	}

	company, err := s.companyService.GetCompanyByID(ctx, *companyID)
	if err != nil {
		s.logger.Error("Failed to get company", map[string]interface{}{
			"company_id": *companyID,
			"error":      err.Error(),
		})
		return nil, fmt.Errorf("failed to get company: %w", err)
	}

	tenders, total, err := s.repository.List(ctx, TenderSearchCriteria{
		Status:          []TenderStatus{TenderStatusActive},
		Classifications: company.Tags.CPVCodes,
	}, limit, offset, from, to)
	if err != nil {
		s.logger.Error("Failed to list tenders", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, fmt.Errorf("failed to list tenders: %w", err)
	}

	tenderResponses := make([]TenderResponse, 0, len(tenders))
	for _, tender := range tenders {
		tenderResponses = append(tenderResponses, *tender.ToTenderResponse())
	}

	return &ListTendersResponse{
		Tenders: tenderResponses,
		Metadata: &response.Meta{
			Total:  int(total),
			Limit:  limit,
			Offset: offset,
			Pages:  int(math.Ceil(float64(total) / float64(limit))),
			Page:   int(math.Ceil(float64(offset) / float64(limit))),
		},
	}, nil
}

// GetTenderStatistics retrieves tender statistics
func (s *tenderService) GetTenderStatistics(ctx context.Context) (*TenderStatistics, error) {
	s.logger.Info("Retrieving tender statistics", map[string]interface{}{})

	stats, err := s.repository.GetStatistics(ctx)
	if err != nil {
		s.logger.Error("Failed to retrieve tender statistics", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, fmt.Errorf("failed to retrieve statistics: %w", err)
	}

	return stats, nil
}

// UpdateExpiredTenders updates status of expired tenders
func (s *tenderService) UpdateExpiredTenders(ctx context.Context) error {
	s.logger.Info("Updating expired tenders", map[string]interface{}{})

	// Get tenders that should be expired
	expiredTenders, err := s.repository.GetExpiredTenders(ctx, time.Now().Unix())
	if err != nil {
		s.logger.Error("Failed to get expired tenders", map[string]interface{}{
			"error": err.Error(),
		})
		return fmt.Errorf("failed to get expired tenders: %w", err)
	}

	updatedCount := 0
	for _, tender := range expiredTenders {
		if tender.Status != TenderStatusExpired {
			tender.UpdateStatus(TenderStatusExpired)
			if err := s.repository.Update(ctx, &tender); err != nil {
				s.logger.Error("Failed to update expired tender", map[string]interface{}{
					"tender_id": tender.ID,
					"error":     err.Error(),
				})
				continue
			}
			updatedCount++
		}
	}

	s.logger.Info("Expired tenders updated", map[string]interface{}{
		"total_expired": len(expiredTenders),
		"updated":       updatedCount,
	})

	return nil
}

// CleanupOldData removes old data based on retention policy
func (s *tenderService) CleanupOldData(ctx context.Context, olderThan time.Duration) error {
	s.logger.Info("Cleaning up old data", map[string]interface{}{
		"older_than": olderThan.String(),
	})

	cutoffTime := time.Now().Add(-olderThan).Unix()

	// For now, we'll just log what would be cleaned up
	// In production, you might want to actually delete old data
	criteria := TenderSearchCriteria{
		Status: []TenderStatus{TenderStatusExpired, TenderStatusCancelled, TenderStatusAwarded},
	}

	oldTenders, err := s.repository.Search(ctx, criteria)
	if err != nil {
		s.logger.Error("Failed to find old tenders for cleanup", map[string]interface{}{
			"error": err.Error(),
		})
		return fmt.Errorf("failed to find old tenders: %w", err)
	}

	cleanupCount := 0
	for _, tender := range oldTenders {
		if tender.CreatedAt < cutoffTime {
			cleanupCount++
			// Here you would delete the tender if implementing actual cleanup
			// s.repository.Delete(ctx, tender.ID)
		}
	}

	s.logger.Info("Old data cleanup completed", map[string]interface{}{
		"found_old_tenders": cleanupCount,
		"cutoff_time":       cutoffTime,
	})

	return nil
}
