package service

import (
	"context"
	"errors"

	"eslogad-be/internal/contract"
	"eslogad-be/internal/entities"
	"eslogad-be/internal/transformer"

	"github.com/google/uuid"
)

type VoteEventRepository interface {
	Create(ctx context.Context, voteEvent *entities.VoteEvent) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.VoteEvent, error)
	GetActiveEvents(ctx context.Context) ([]*entities.VoteEvent, error)
	List(ctx context.Context, limit, offset int) ([]*entities.VoteEvent, int64, error)
	Update(ctx context.Context, voteEvent *entities.VoteEvent) error
	Delete(ctx context.Context, id uuid.UUID) error
	CreateCandidate(ctx context.Context, candidate *entities.Candidate) error
	GetCandidatesByEventID(ctx context.Context, eventID uuid.UUID) ([]*entities.Candidate, error)
	SubmitVote(ctx context.Context, vote *entities.Vote) error
	HasUserVoted(ctx context.Context, userID, eventID uuid.UUID) (bool, error)
	GetVoteResults(ctx context.Context, eventID uuid.UUID) (map[uuid.UUID]int64, error)
}

type VoteEventServiceImpl struct {
	voteEventRepo VoteEventRepository
}

func NewVoteEventService(voteEventRepo VoteEventRepository) *VoteEventServiceImpl {
	return &VoteEventServiceImpl{
		voteEventRepo: voteEventRepo,
	}
}

func (s *VoteEventServiceImpl) CreateVoteEvent(ctx context.Context, req *contract.CreateVoteEventRequest) (*contract.VoteEventResponse, error) {
	if req.EndDate.Before(req.StartDate) {
		return nil, errors.New("end date must be after start date")
	}

	voteEvent := &entities.VoteEvent{
		Title:       req.Title,
		Description: req.Description,
		StartDate:   req.StartDate,
		EndDate:     req.EndDate,
		IsActive:    true,
		ResultsOpen: false,
	}

	if req.ResultsOpen != nil {
		voteEvent.ResultsOpen = *req.ResultsOpen
	}

	if err := s.voteEventRepo.Create(ctx, voteEvent); err != nil {
		return nil, err
	}

	return transformer.VoteEventToContract(voteEvent), nil
}

func (s *VoteEventServiceImpl) GetVoteEventByID(ctx context.Context, id uuid.UUID) (*contract.VoteEventResponse, error) {
	voteEvent, err := s.voteEventRepo.GetByID(ctx, id)
	if err != nil {
		return nil, err
	}

	return transformer.VoteEventToContract(voteEvent), nil
}

func (s *VoteEventServiceImpl) GetActiveEvents(ctx context.Context) ([]contract.VoteEventResponse, error) {
	events, err := s.voteEventRepo.GetActiveEvents(ctx)
	if err != nil {
		return nil, err
	}

	var responses []contract.VoteEventResponse
	for _, event := range events {
		responses = append(responses, *transformer.VoteEventToContract(event))
	}

	return responses, nil
}

func (s *VoteEventServiceImpl) ListVoteEvents(ctx context.Context, req *contract.ListVoteEventsRequest) (*contract.ListVoteEventsResponse, error) {
	page := req.Page
	if page <= 0 {
		page = 1
	}
	limit := req.Limit
	if limit <= 0 {
		limit = 10
	}

	offset := (page - 1) * limit

	events, total, err := s.voteEventRepo.List(ctx, limit, offset)
	if err != nil {
		return nil, err
	}

	var responses []contract.VoteEventResponse
	for _, event := range events {
		responses = append(responses, *transformer.VoteEventToContract(event))
	}

	return &contract.ListVoteEventsResponse{
		VoteEvents: responses,
		Pagination: transformer.CreatePaginationResponse(int(total), page, limit),
	}, nil
}

func (s *VoteEventServiceImpl) UpdateVoteEvent(ctx context.Context, id uuid.UUID, req *contract.UpdateVoteEventRequest) (*contract.VoteEventResponse, error) {
	if req.EndDate.Before(req.StartDate) {
		return nil, errors.New("end date must be after start date")
	}

	voteEvent, err := s.voteEventRepo.GetByID(ctx, id)
	if err != nil {
		return nil, err
	}

	voteEvent.Title = req.Title
	voteEvent.Description = req.Description
	voteEvent.StartDate = req.StartDate
	voteEvent.EndDate = req.EndDate
	voteEvent.IsActive = req.IsActive

	if req.ResultsOpen != nil {
		voteEvent.ResultsOpen = *req.ResultsOpen
	}

	if err := s.voteEventRepo.Update(ctx, voteEvent); err != nil {
		return nil, err
	}

	return transformer.VoteEventToContract(voteEvent), nil
}

func (s *VoteEventServiceImpl) DeleteVoteEvent(ctx context.Context, id uuid.UUID) error {
	return s.voteEventRepo.Delete(ctx, id)
}

func (s *VoteEventServiceImpl) CreateCandidate(ctx context.Context, req *contract.CreateCandidateRequest) (*contract.CandidateResponse, error) {
	voteEvent, err := s.voteEventRepo.GetByID(ctx, req.VoteEventID)
	if err != nil {
		return nil, err
	}

	if !voteEvent.IsActive {
		return nil, errors.New("cannot add candidates to inactive vote event")
	}

	candidate := &entities.Candidate{
		VoteEventID: req.VoteEventID,
		Name:        req.Name,
		ImageURL:    req.ImageURL,
		Description: req.Description,
	}

	if err := s.voteEventRepo.CreateCandidate(ctx, candidate); err != nil {
		return nil, err
	}

	return transformer.CandidateToContract(candidate), nil
}

func (s *VoteEventServiceImpl) GetCandidates(ctx context.Context, eventID uuid.UUID) ([]contract.CandidateResponse, error) {
	candidates, err := s.voteEventRepo.GetCandidatesByEventID(ctx, eventID)
	if err != nil {
		return nil, err
	}

	var responses []contract.CandidateResponse
	for _, candidate := range candidates {
		responses = append(responses, *transformer.CandidateToContract(candidate))
	}

	return responses, nil
}

func (s *VoteEventServiceImpl) SubmitVote(ctx context.Context, userID uuid.UUID, req *contract.SubmitVoteRequest) (*contract.VoteResponse, error) {
	voteEvent, err := s.voteEventRepo.GetByID(ctx, req.VoteEventID)
	if err != nil {
		return nil, err
	}

	if !voteEvent.IsVotingOpen() {
		return nil, errors.New("voting is not open for this event")
	}

	hasVoted, err := s.voteEventRepo.HasUserVoted(ctx, userID, req.VoteEventID)
	if err != nil {
		return nil, err
	}

	if hasVoted {
		return nil, errors.New("user has already voted for this event")
	}

	vote := &entities.Vote{
		VoteEventID: req.VoteEventID,
		CandidateID: req.CandidateID,
		UserID:      userID,
	}

	if err := s.voteEventRepo.SubmitVote(ctx, vote); err != nil {
		return nil, err
	}

	return transformer.VoteToContract(vote), nil
}

func (s *VoteEventServiceImpl) GetVoteResults(ctx context.Context, eventID uuid.UUID) (*contract.VoteResultsResponse, error) {

	candidates, err := s.voteEventRepo.GetCandidatesByEventID(ctx, eventID)
	if err != nil {
		return nil, err
	}

	voteResults, err := s.voteEventRepo.GetVoteResults(ctx, eventID)
	if err != nil {
		return nil, err
	}

	var candidatesWithVotes []contract.CandidateWithVotesResponse
	var totalVotes int64

	for _, candidate := range candidates {
		voteCount := voteResults[candidate.ID]
		totalVotes += voteCount

		candidatesWithVotes = append(candidatesWithVotes, contract.CandidateWithVotesResponse{
			CandidateResponse: *transformer.CandidateToContract(candidate),
			VoteCount:         voteCount,
		})
	}

	return &contract.VoteResultsResponse{
		VoteEventID: eventID,
		Candidates:  candidatesWithVotes,
		TotalVotes:  totalVotes,
	}, nil
}

func (s *VoteEventServiceImpl) CheckVoteStatus(ctx context.Context, userID, eventID uuid.UUID) (*contract.CheckVoteStatusResponse, error) {
	hasVoted, err := s.voteEventRepo.HasUserVoted(ctx, userID, eventID)
	if err != nil {
		return nil, err
	}

	response := &contract.CheckVoteStatusResponse{
		HasVoted: hasVoted,
	}

	return response, nil
}
