package feedback

import (
	"context"
	"errors"
	"tm/internal/company"
	"tm/internal/tender"
	"tm/pkg/logger"
	"tm/pkg/response"
)

// FeedbackService interface defines feedback business logic methods
type Service interface {
	Toggle(ctx context.Context, req ToggleFeedbackForm, customerID *string, companyID *string) (*FeedbackResponse, error)
	Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) (*FeedbackListResponse, error)
	Get(ctx context.Context, id string) (*FeedbackResponse, error)
	GetByTenderID(ctx context.Context, tenderID, companyID string) (*FeedbackResponse, error)
	GetByTenderIDForCustomer(ctx context.Context, tenderID, customerID string) (*FeedbackResponse, error)
	Delete(ctx context.Context, id string) error
	CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error)
	CustomerStats(ctx context.Context, customerID string) (*CustomerStatsResponse, error)
	getDetails(ctx context.Context, tenderID, companyID string) (*tenderResponse, *companyResponse)
}

// feedbackService implements FeedbackService interface
type feedbackService struct {
	feedbackRepo   Repository
	tenderService  tender.Service
	companyService company.Service
	logger         logger.Logger
}

// NewService creates a new feedback service
func NewService(feedbackRepo Repository, tenderService tender.Service, companyService company.Service, logger logger.Logger) Service {
	return &feedbackService{
		feedbackRepo:   feedbackRepo,
		tenderService:  tenderService,
		companyService: companyService,
		logger:         logger,
	}
}

func (s *feedbackService) Toggle(ctx context.Context, req ToggleFeedbackForm, customerID *string, companyID *string) (*FeedbackResponse, error) {
	existing, err := s.feedbackRepo.GetByCustomerID(ctx, *customerID, req.Tender)

	if err != nil {
		feedback := &Feedback{
			TenderID:     req.Tender,
			FeedbackType: req.Type,
			CompanyID:    companyID,
			CustomerID:   customerID,
		}

		err = s.feedbackRepo.Create(ctx, feedback)
		if err != nil {
			s.logger.Error("Failed to create new feedback", map[string]interface{}{
				"tender_id":   req.Tender,
				"customer_id": *customerID,
				"company_id":  *companyID,
				"error":       err.Error(),
			})
			return nil, err
		}

		tender, company := s.getDetails(ctx, req.Tender, *companyID)

		return feedback.ToResponse(tender, company), nil
	}

	if existing.FeedbackType == req.Type {
		err = s.feedbackRepo.Delete(ctx, existing.GetID())
		if err != nil {
			s.logger.Error("Failed to delete existing feedback", map[string]interface{}{
				"feedback_id": existing.GetID(),
				"tender_id":   req.Tender,
				"customer_id": *customerID,
				"error":       err.Error(),
			})
			return nil, err
		}

		if req.Type == FeedbackTypeLike {
			existing.FeedbackType = "unlike"
		} else {
			existing.FeedbackType = "undislike"
		}

		tender, company := s.getDetails(ctx, req.Tender, *companyID)

		return existing.ToResponse(tender, company), nil
	}

	if existing.FeedbackType == FeedbackTypeLike {
		existing.FeedbackType = FeedbackTypeDislike
	} else {
		existing.FeedbackType = FeedbackTypeLike
	}

	err = s.feedbackRepo.Update(ctx, existing)
	if err != nil {
		s.logger.Error("Failed to update existing feedback", map[string]interface{}{
			"feedback_id": existing.GetID(),
			"tender_id":   req.Tender,
			"customer_id": *customerID,
			"error":       err.Error(),
		})
		return nil, err
	}

	tender, company := s.getDetails(ctx, req.Tender, *companyID)

	return existing.ToResponse(tender, company), nil
}

func (s *feedbackService) Get(ctx context.Context, id string) (*FeedbackResponse, error) {
	feedback, err := s.feedbackRepo.GetByID(ctx, id)
	if err != nil {
		s.logger.Error("Failed to get feedback", map[string]interface{}{
			"id":    id,
			"error": err.Error(),
		})
		return nil, err
	}

	tender, company := s.getDetails(ctx, feedback.TenderID, feedbackCompanyID(*feedback))

	return feedback.ToResponse(tender, company), nil
}

func (s *feedbackService) Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) (*FeedbackListResponse, error) {
	page, err := s.feedbackRepo.Search(ctx, criteria, pagination)
	if err != nil {
		s.logger.Error("Failed to list feedback", map[string]interface{}{
			"criteria": criteria,
			"limit":    pagination.Limit,
			"offset":   pagination.Offset,
			"error":    err.Error(),
		})
		return nil, err
	}

	meta := pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore, page.PageOffset)

	tenderByID, companyByID := s.loadListDetails(ctx, page.Items)

	result := make([]*FeedbackResponse, len(page.Items))
	for i, feedback := range page.Items {
		var tenderResp *tenderResponse
		if t, ok := tenderByID[feedback.TenderID]; ok {
			tenderResp = t
		}
		var companyResp *companyResponse
		if cid := feedbackCompanyID(feedback); cid != "" {
			companyResp = companyByID[cid]
		}
		result[i] = feedback.ToResponse(tenderResp, companyResp)
	}

	return &FeedbackListResponse{
		Feedback: result,
		Meta:     meta,
	}, nil
}

func (s *feedbackService) Delete(ctx context.Context, id string) error {
	return s.feedbackRepo.Delete(ctx, id)
}

func (s *feedbackService) GetByTenderID(ctx context.Context, tenderID, companyID string) (*FeedbackResponse, error) {
	feedback, err := s.feedbackRepo.GetByTenderID(ctx, tenderID, companyID)
	if err != nil {
		if !errors.Is(err, ErrFeedbackNotFound) {
			s.logger.Error("Failed to get feedback by tender ID", map[string]interface{}{
				"tender_id":  tenderID,
				"company_id": companyID,
				"error":      err.Error(),
			})
		}
		return nil, err
	}

	tender, company := s.getDetails(ctx, feedback.TenderID, feedbackCompanyID(*feedback))

	return feedback.ToResponse(tender, company), nil
}

func (s *feedbackService) GetByTenderIDForCustomer(ctx context.Context, tenderID, customerID string) (*FeedbackResponse, error) {
	feedback, err := s.feedbackRepo.GetByTenderIDForCustomer(ctx, tenderID, customerID)
	if err != nil {
		if !errors.Is(err, ErrFeedbackNotFound) {
			s.logger.Error("Failed to get feedback by tender ID for customer", map[string]interface{}{
				"tender_id":   tenderID,
				"customer_id": customerID,
				"error":       err.Error(),
			})
		}
		return nil, err
	}

	tender, company := s.getDetails(ctx, feedback.TenderID, feedbackCompanyID(*feedback))

	return feedback.ToResponse(tender, company), nil
}

func feedbackCompanyID(f Feedback) string {
	if f.CompanyID == nil {
		return ""
	}
	return *f.CompanyID
}

func uniqueNonEmptyStrings(values []string) []string {
	seen := make(map[string]struct{}, len(values))
	out := make([]string, 0, len(values))
	for _, v := range values {
		if v == "" {
			continue
		}
		if _, ok := seen[v]; ok {
			continue
		}
		seen[v] = struct{}{}
		out = append(out, v)
	}
	return out
}

func collectFeedbackListIDs(items []Feedback) (tenderIDs, companyIDs []string) {
	tenderIDs = make([]string, 0, len(items))
	companyIDs = make([]string, 0, len(items))
	for _, item := range items {
		tenderIDs = append(tenderIDs, item.TenderID)
		companyIDs = append(companyIDs, feedbackCompanyID(item))
	}
	return uniqueNonEmptyStrings(tenderIDs), uniqueNonEmptyStrings(companyIDs)
}

// loadListDetails batch-loads tender and company data for feedback list responses.
func (s *feedbackService) loadListDetails(ctx context.Context, items []Feedback) (
	map[string]*tenderResponse,
	map[string]*companyResponse,
) {
	tenderByID := make(map[string]*tenderResponse)
	companyByID := make(map[string]*companyResponse)

	if len(items) == 0 {
		return tenderByID, companyByID
	}

	tenderIDs, companyIDs := collectFeedbackListIDs(items)

	if len(tenderIDs) > 0 {
		tenders, err := s.tenderService.GetByIDs(ctx, tenderIDs)
		if err != nil {
			s.logger.Error("Failed to batch load tenders for feedback list", map[string]interface{}{
				"count": len(tenderIDs),
				"error": err.Error(),
			})
		} else {
			for id, t := range tenders {
				tenderByID[id] = tenderToFeedbackResponse(t)
			}
		}
	}

	if len(companyIDs) > 0 {
		companies, err := s.companyService.GetNamesByIDs(ctx, companyIDs)
		if err != nil {
			s.logger.Error("Failed to batch load companies for feedback list", map[string]interface{}{
				"count": len(companyIDs),
				"error": err.Error(),
			})
		} else {
			for id, c := range companies {
				companyByID[id] = companyToFeedbackResponse(c)
			}
		}
	}

	return tenderByID, companyByID
}

func tenderToFeedbackResponse(t *tender.TenderResponse) *tenderResponse {
	if t == nil {
		return nil
	}

	var buyer *organizationResponse
	if t.BuyerOrganization != nil {
		buyer = &organizationResponse{Name: t.BuyerOrganization.Name}
	}

	return &tenderResponse{
		ID:                  t.ID,
		NoticePublicationID: t.NoticePublicationID,
		PublicationDate:     t.PublicationDate,
		Title:               t.Title,
		Description:         t.Description,
		ProcurementTypeCode: t.ProcurementTypeCode,
		ProcedureCode:       t.ProcedureCode,
		EstimatedValue:      t.EstimatedValue,
		Currency:            t.Currency,
		Duration:            t.Duration,
		DurationUnit:        t.DurationUnit,
		TenderDeadline:      t.TenderDeadline,
		SubmissionDeadline:  t.SubmissionDeadline,
		CountryCode:         t.CountryCode,
		BuyerOrganization:   buyer,
		Status:              string(t.Status),
	}
}

func companyToFeedbackResponse(c *company.CompanyResponse) *companyResponse {
	if c == nil {
		return nil
	}
	return &companyResponse{
		ID:   c.ID,
		Name: c.Name,
	}
}

func (s *feedbackService) getDetails(ctx context.Context, tenderID, companyID string) (*tenderResponse, *companyResponse) {
	var tenderResp *tenderResponse
	tender, err := s.tenderService.GetByID(ctx, tenderID, "")
	if err != nil {
		s.logger.Error("Failed to get tender", map[string]interface{}{
			"tender_id": tenderID,
			"error":     err.Error(),
		})
	} else {
		tenderResp = tenderToFeedbackResponse(tender)
	}

	var companyResp *companyResponse
	if companyID != "" {
		company, err := s.companyService.GetByID(ctx, companyID)
		if err != nil {
			s.logger.Error("Failed to get company", map[string]interface{}{
				"company_id": companyID,
				"error":      err.Error(),
			})
		} else {
			companyResp = companyToFeedbackResponse(company)
		}
	}

	return tenderResp, companyResp
}

func (s *feedbackService) CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error) {
	s.logger.Info("Getting company feedback statistics", map[string]interface{}{
		"company_id": companyID,
	})

	stats, err := s.feedbackRepo.CompanyStats(ctx, companyID)
	if err != nil {
		s.logger.Error("Failed to get company stats", map[string]interface{}{
			"company_id": companyID,
			"error":      err.Error(),
		})
		return nil, err
	}

	s.logger.Info("Company stats retrieved successfully", map[string]interface{}{
		"company_id":     companyID,
		"total_feedback": stats.TotalFeedback,
		"total_likes":    stats.TotalLikes,
		"total_dislikes": stats.TotalDislikes,
	})

	return stats, nil
}

func (s *feedbackService) CustomerStats(ctx context.Context, customerID string) (*CustomerStatsResponse, error) {
	s.logger.Info("Getting customer feedback statistics", map[string]interface{}{
		"customer_id": customerID,
	})

	stats, err := s.feedbackRepo.CustomerStats(ctx, customerID)
	if err != nil {
		s.logger.Error("Failed to get customer stats", map[string]interface{}{
			"customer_id": customerID,
			"error":       err.Error(),
		})
		return nil, err
	}

	s.logger.Info("Customer stats retrieved successfully", map[string]interface{}{
		"customer_id":    customerID,
		"total_feedback": stats.TotalFeedback,
		"total_likes":    stats.TotalLikes,
		"total_dislikes": stats.TotalDislikes,
	})

	return stats, nil
}
