package processor

import (
	"context"
	"fmt"
	"time"

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

	"github.com/google/uuid"
)

func (p *LetterProcessorImpl) GetDepartmentDispositionStatus(ctx context.Context, req *contract.GetDepartmentDispositionStatusRequest) (*contract.ListDepartmentDispositionStatusResponse, error) {
	dispositions, err := p.dispositionDeptRepo.GetByLetterIncomingID(ctx, req.LetterIncomingID)
	if err != nil {
		return nil, err
	}

	var response []contract.DepartmentDispositionStatusResponse
	for _, disp := range dispositions {
		letterResp := transformer.LetterIncomingEntityToContract(disp.LetterIncoming)

		var fromDept *contract.DepartmentResponse
		if disp.LetterIncomingDisposition != nil && disp.LetterIncomingDisposition.DepartmentID != nil {
			fromDept = transformer.DepartmentEntityToContract(&disp.LetterIncomingDisposition.Department)
		}

		response = append(response, contract.DepartmentDispositionStatusResponse{
			ID:               disp.ID,
			LetterID:         disp.LetterIncomingID,
			Letter:           letterResp,
			FromDepartmentID: disp.LetterIncomingDisposition.DepartmentID,
			FromDepartment:   fromDept,
			ToDepartmentID:   disp.DepartmentID,
			ToDepartment:     transformer.DepartmentEntityToContract(disp.Department),
			Status:           string(disp.Status),
			Notes:            disp.LetterIncomingDisposition.Notes,
			ReadAt:           disp.ReadAt,
			CompletedAt:      disp.CompletedAt,
			CreatedAt:        disp.CreatedAt,
			UpdatedAt:        disp.UpdatedAt,
		})
	}

	return &contract.ListDepartmentDispositionStatusResponse{
		Dispositions: response,
		Pagination: contract.PaginationResponse{
			TotalCount: len(response),
			Page:       1,
			Limit:      len(response),
			TotalPages: 1,
		},
	}, nil
}

func (p *LetterProcessorImpl) UpdateDispositionStatus(ctx context.Context, req *contract.UpdateDispositionStatusRequest) (*contract.DepartmentDispositionStatusResponse, error) {
	var result *contract.DepartmentDispositionStatusResponse

	err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
		userID := appcontext.FromGinContext(txCtx).UserID
		departmentID := appcontext.FromGinContext(txCtx).DepartmentID

		dispDept, err := p.dispositionDeptRepo.GetByDispositionAndDepartment(txCtx, req.LetterIncomingID, departmentID)
		if err != nil {
			return err
		}

		notes := ""
		if req.Notes != nil {
			notes = *req.Notes
		}
		if err := p.updateDispositionDepartmentStatus(txCtx, dispDept.ID, req.Status, notes); err != nil {
			return err
		}

		p.activity.LogLetterDispositionStatusUpdate(txCtx, req.LetterIncomingID, userID, req.Status)

		if err := p.checkAndUpdateLetterCompletionStatus(txCtx, req.LetterIncomingID); err != nil {
			return err
		}

		updatedDispDept, err := p.dispositionDeptRepo.GetByID(txCtx, dispDept.ID)
		if err != nil {
			return err
		}

		result = p.buildDispositionStatusResponse(updatedDispDept)
		return nil
	})

	if err != nil {
		return nil, err
	}

	return result, nil
}

// updateDispositionDepartmentStatus updates the status of a disposition department
func (p *LetterProcessorImpl) updateDispositionDepartmentStatus(ctx context.Context, dispDeptID uuid.UUID, status, notes string) error {
	now := time.Now()
	var dispositionStatus entities.LetterIncomingDispositionDepartmentStatus
	var readAt, completedAt *time.Time

	switch status {
	case "completed":
		dispositionStatus = entities.DispositionDepartmentStatusCompleted
		completedAt = &now
		readAt = &now // Mark as read when completing
	case "read":
		dispositionStatus = entities.DispositionDepartmentStatusRead
		readAt = &now
	default:
		dispositionStatus = entities.DispositionDepartmentStatusPending
	}

	return p.dispositionDeptRepo.UpdateStatus(ctx, dispDeptID, dispositionStatus, notes, readAt, completedAt)
}

// addDispositionNoteIfProvided adds a note to the disposition if provided
func (p *LetterProcessorImpl) addDispositionNoteIfProvided(ctx context.Context, dispositionID uuid.UUID, userID uuid.UUID, notes *string) error {
	if notes == nil || *notes == "" {
		return nil
	}

	note := &entities.DispositionNote{
		DispositionID: dispositionID,
		UserID:        &userID,
		Note:          *notes,
	}

	return p.dispositionNoteRepo.Create(ctx, note)
}

func (p *LetterProcessorImpl) checkAndUpdateLetterCompletionStatus(ctx context.Context, letterIncomingID uuid.UUID) error {
	dispositions, err := p.dispositionDeptRepo.GetByLetterIncomingID(ctx, letterIncomingID)
	if err != nil {
		return err
	}

	allCompleted := true
	for _, disp := range dispositions {
		if disp.Status == entities.DispositionDepartmentStatusPending {
			allCompleted = false
			break
		}
	}

	if allCompleted && len(dispositions) > 0 {
		letter, err := p.letterRepo.GetByID(ctx, letterIncomingID)
		if err != nil {
			return err
		}

		letter.Status = "completed"
		if err := p.letterRepo.Update(ctx, letter); err != nil {
			return err
		}
	}

	return nil
}

// buildDispositionStatusResponse builds the response for disposition status
func (p *LetterProcessorImpl) buildDispositionStatusResponse(dispDept *entities.LetterIncomingDispositionDepartment) *contract.DepartmentDispositionStatusResponse {
	letterResp := transformer.LetterIncomingEntityToContract(dispDept.LetterIncoming)

	var fromDept *contract.DepartmentResponse
	if dispDept.LetterIncomingDisposition != nil && dispDept.LetterIncomingDisposition.DepartmentID != nil {
		fromDept = transformer.DepartmentEntityToContract(&dispDept.LetterIncomingDisposition.Department)
	}

	return &contract.DepartmentDispositionStatusResponse{
		ID:               dispDept.ID,
		LetterID:         dispDept.LetterIncomingID,
		Letter:           letterResp,
		FromDepartmentID: dispDept.LetterIncomingDisposition.DepartmentID,
		FromDepartment:   fromDept,
		ToDepartmentID:   dispDept.DepartmentID,
		ToDepartment:     transformer.DepartmentEntityToContract(dispDept.Department),
		Status:           string(dispDept.Status),
		Notes:            dispDept.LetterIncomingDisposition.Notes,
		ReadAt:           dispDept.ReadAt,
		CompletedAt:      dispDept.CompletedAt,
		CreatedAt:        dispDept.CreatedAt,
		UpdatedAt:        dispDept.UpdatedAt,
	}
}

func (p *LetterProcessorImpl) GetLetterCTA(ctx context.Context, letterIncomingID uuid.UUID, departmentID uuid.UUID) (*contract.LetterCTAResponse, error) {
	letter, err := p.letterRepo.GetByID(ctx, letterIncomingID)
	if err != nil {
		return nil, err
	}

	response := &contract.LetterCTAResponse{
		LetterIncomingID: letterIncomingID,
		Actions:          []contract.LetterCTAAction{},
		Message:          "",
	}

	isEligibleForDispo, err := p.dispoRoutes.IsEligibleForDisposition(ctx, departmentID)
	if err != nil {
		return nil, err
	}

	if letter.Status == "completed" || letter.Status == "archived" {
		response.Message = "Letter is no longer accepting actions"
		return response, nil
	}

	dispDepts, err := p.dispositionDeptRepo.GetByLetterAndDepartment(ctx, letterIncomingID, departmentID)
	if err != nil {
		return nil, err
	}

	if len(dispDepts) == 0 {
		response.Message = "Your department is not a recipient of this letter"
		return response, nil
	}

	for _, dispDept := range dispDepts {
		if dispDept.Status == entities.DispositionDepartmentStatusPending {
			response.DispositionID = &dispDept.LetterIncomingDispositionID
			currentStatus := string(dispDept.Status)
			response.CurrentStatus = &currentStatus

			if isEligibleForDispo {
				response.Actions = append(response.Actions, contract.LetterCTAAction{
					Type:        "create_disposition",
					Label:       "Disposisi",
					Path:        fmt.Sprintf("/api/v1/letters/%s/dispositions", letterIncomingID),
					Method:      "POST",
					Description: "Create a new disposition for this letter",
				})
			}

			response.Actions = append(response.Actions, contract.LetterCTAAction{
				Type:        "update_status",
				Label:       "Tindak Lanjut",
				Path:        fmt.Sprintf("/api/v1/letters/dispositions/%s/status", response.LetterIncomingID),
				Method:      "PUT",
				Description: "Update the status of your disposition",
			})
		}
	}

	return response, nil
}
