package processor

import (
	"context"
	"fmt"
	"time"

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

	"github.com/google/uuid"
)

type LetterProcessorImpl struct {
	letterRepo               *repository.LetterIncomingRepository
	attachRepo               *repository.LetterIncomingAttachmentRepository
	txManager                *repository.TxManager
	activity                 *ActivityLogProcessorImpl
	dispositionRepo          *repository.LetterIncomingDispositionRepository
	dispositionDeptRepo      *repository.LetterIncomingDispositionDepartmentRepository
	dispositionActionSelRepo *repository.LetterDispositionActionSelectionRepository
	dispositionNoteRepo      *repository.DispositionNoteRepository
	discussionRepo           *repository.LetterDiscussionRepository
	settingRepo              *repository.AppSettingRepository
	recipientRepo            *repository.LetterIncomingRecipientRepository
	departmentRepo           *repository.DepartmentRepository
	userDeptRepo             *repository.UserDepartmentRepository
	priorityRepo             *repository.PriorityRepository
	institutionRepo          *repository.InstitutionRepository
	dispActionRepo           *repository.DispositionActionRepository
}

func NewLetterProcessor(letterRepo *repository.LetterIncomingRepository, attachRepo *repository.LetterIncomingAttachmentRepository, txManager *repository.TxManager, activity *ActivityLogProcessorImpl, dispRepo *repository.LetterIncomingDispositionRepository, dispDeptRepo *repository.LetterIncomingDispositionDepartmentRepository, dispSelRepo *repository.LetterDispositionActionSelectionRepository, noteRepo *repository.DispositionNoteRepository, discussionRepo *repository.LetterDiscussionRepository, settingRepo *repository.AppSettingRepository, recipientRepo *repository.LetterIncomingRecipientRepository, departmentRepo *repository.DepartmentRepository, userDeptRepo *repository.UserDepartmentRepository, priorityRepo *repository.PriorityRepository, institutionRepo *repository.InstitutionRepository, dispActionRepo *repository.DispositionActionRepository) *LetterProcessorImpl {
	return &LetterProcessorImpl{letterRepo: letterRepo, attachRepo: attachRepo, txManager: txManager, activity: activity, dispositionRepo: dispRepo, dispositionDeptRepo: dispDeptRepo, dispositionActionSelRepo: dispSelRepo, dispositionNoteRepo: noteRepo, discussionRepo: discussionRepo, settingRepo: settingRepo, recipientRepo: recipientRepo, departmentRepo: departmentRepo, userDeptRepo: userDeptRepo, priorityRepo: priorityRepo, institutionRepo: institutionRepo, dispActionRepo: dispActionRepo}
}

func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
	var result *contract.IncomingLetterResponse
	err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
		userID := appcontext.FromGinContext(txCtx).UserID

		prefix := "ESLI"
		seq := 0
		if s, err := p.settingRepo.Get(txCtx, contract.SettingIncomingLetterPrefix); err == nil {
			if v, ok := s.Value["value"].(string); ok && v != "" {
				prefix = v
			}
		}
		if s, err := p.settingRepo.Get(txCtx, contract.SettingIncomingLetterSequence); err == nil {
			if v, ok := s.Value["value"].(float64); ok {
				seq = int(v)
			}
		}
		seq = seq + 1
		letterNumber := fmt.Sprintf("%s%04d", prefix, seq)

		entity := &entities.LetterIncoming{
			ReferenceNumber:     req.ReferenceNumber,
			Subject:             req.Subject,
			Description:         req.Description,
			PriorityID:          req.PriorityID,
			SenderInstitutionID: req.SenderInstitutionID,
			ReceivedDate:        req.ReceivedDate,
			DueDate:             req.DueDate,
			Status:              entities.LetterIncomingStatusNew,
			CreatedBy:           userID,
		}
		entity.LetterNumber = letterNumber
		if err := p.letterRepo.Create(txCtx, entity); err != nil {
			return err
		}

		_ = p.settingRepo.Upsert(txCtx, contract.SettingIncomingLetterSequence, entities.JSONB{"value": seq})

		defaultDeptCodes := []string{}
		if s, err := p.settingRepo.Get(txCtx, contract.SettingIncomingLetterRecipients); err == nil {
			if arr, ok := s.Value["department_codes"].([]interface{}); ok {
				for _, it := range arr {
					if str, ok := it.(string); ok {
						defaultDeptCodes = append(defaultDeptCodes, str)
					}
				}
			}
		}

		depIDs := make([]uuid.UUID, 0, len(defaultDeptCodes))
		for _, code := range defaultDeptCodes {
			dep, err := p.departmentRepo.GetByCode(txCtx, code)
			if err != nil {
				continue
			}
			depIDs = append(depIDs, dep.ID)
		}

		userMemberships, _ := p.userDeptRepo.ListActiveByDepartmentIDs(txCtx, depIDs)
		var recipients []entities.LetterIncomingRecipient

		mapsUsers := map[string]bool{}
		for _, row := range userMemberships {
			uid := row.UserID
			if _, ok := mapsUsers[uid.String()]; !ok {
				recipients = append(recipients, entities.LetterIncomingRecipient{LetterID: entity.ID, RecipientUserID: &uid, RecipientDepartmentID: &row.DepartmentID, Status: entities.RecipientStatusNew})
			}
			mapsUsers[uid.String()] = true
		}

		if len(recipients) > 0 {
			if err := p.recipientRepo.CreateBulk(txCtx, recipients); err != nil {
				return err
			}
		}

		if p.activity != nil {
			action := "letter.created"
			if err := p.activity.Log(txCtx, entity.ID, action, &userID, nil, nil, nil, nil, nil, map[string]interface{}{"letter_number": letterNumber}); err != nil {
				return err
			}
		}

		attachments := make([]entities.LetterIncomingAttachment, 0, len(req.Attachments))
		for _, a := range req.Attachments {
			attachments = append(attachments, entities.LetterIncomingAttachment{LetterID: entity.ID, FileURL: a.FileURL, FileName: a.FileName, FileType: a.FileType, UploadedBy: &userID})
		}
		if len(attachments) > 0 {
			if err := p.attachRepo.CreateBulk(txCtx, attachments); err != nil {
				return err
			}
			if p.activity != nil {
				action := "attachment.uploaded"
				for _, a := range attachments {
					ctxMap := map[string]interface{}{"file_name": a.FileName, "file_type": a.FileType}
					if err := p.activity.Log(txCtx, entity.ID, action, &userID, nil, nil, nil, nil, nil, ctxMap); err != nil {
						return err
					}
				}
			}
		}

		savedAttachments, _ := p.attachRepo.ListByLetter(txCtx, entity.ID)
		var pr *entities.Priority
		if entity.PriorityID != nil {
			if p.priorityRepo != nil {
				if got, err := p.priorityRepo.Get(txCtx, *entity.PriorityID); err == nil {
					pr = got
				}
			}
		}
		var inst *entities.Institution
		if entity.SenderInstitutionID != nil {
			if p.institutionRepo != nil {
				if got, err := p.institutionRepo.Get(txCtx, *entity.SenderInstitutionID); err == nil {
					inst = got
				}
			}
		}
		result = transformer.LetterEntityToContract(entity, savedAttachments, pr, inst)
		return nil
	})

	if err != nil {
		return nil, err
	}
	return result, nil
}

func (p *LetterProcessorImpl) GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error) {
	entity, err := p.letterRepo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	atts, _ := p.attachRepo.ListByLetter(ctx, id)
	var pr *entities.Priority
	if entity.PriorityID != nil && p.priorityRepo != nil {
		if got, err := p.priorityRepo.Get(ctx, *entity.PriorityID); err == nil {
			pr = got
		}
	}
	var inst *entities.Institution
	if entity.SenderInstitutionID != nil && p.institutionRepo != nil {
		if got, err := p.institutionRepo.Get(ctx, *entity.SenderInstitutionID); err == nil {
			inst = got
		}
	}
	return transformer.LetterEntityToContract(entity, atts, pr, inst), nil
}

func (p *LetterProcessorImpl) ListIncomingLetters(ctx context.Context, req *contract.ListIncomingLettersRequest) (*contract.ListIncomingLettersResponse, error) {
	page, limit := req.Page, req.Limit
	if page <= 0 {
		page = 1
	}
	if limit <= 0 {
		limit = 10
	}
	filter := repository.ListIncomingLettersFilter{Status: req.Status, Query: req.Query}
	list, total, err := p.letterRepo.List(ctx, filter, limit, (page-1)*limit)
	if err != nil {
		return nil, err
	}
	respList := make([]contract.IncomingLetterResponse, 0, len(list))
	for _, e := range list {
		atts, _ := p.attachRepo.ListByLetter(ctx, e.ID)
		var pr *entities.Priority
		if e.PriorityID != nil && p.priorityRepo != nil {
			if got, err := p.priorityRepo.Get(ctx, *e.PriorityID); err == nil {
				pr = got
			}
		}
		var inst *entities.Institution
		if e.SenderInstitutionID != nil && p.institutionRepo != nil {
			if got, err := p.institutionRepo.Get(ctx, *e.SenderInstitutionID); err == nil {
				inst = got
			}
		}
		resp := transformer.LetterEntityToContract(&e, atts, pr, inst)
		respList = append(respList, *resp)
	}
	return &contract.ListIncomingLettersResponse{Letters: respList, Pagination: transformer.CreatePaginationResponse(int(total), page, limit)}, nil
}

func (p *LetterProcessorImpl) UpdateIncomingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
	var out *contract.IncomingLetterResponse
	err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
		entity, err := p.letterRepo.Get(txCtx, id)
		if err != nil {
			return err
		}
		fromStatus := string(entity.Status)
		if req.ReferenceNumber != nil {
			entity.ReferenceNumber = req.ReferenceNumber
		}
		if req.Subject != nil {
			entity.Subject = *req.Subject
		}
		if req.Description != nil {
			entity.Description = req.Description
		}
		if req.PriorityID != nil {
			entity.PriorityID = req.PriorityID
		}
		if req.SenderInstitutionID != nil {
			entity.SenderInstitutionID = req.SenderInstitutionID
		}
		if req.ReceivedDate != nil {
			entity.ReceivedDate = *req.ReceivedDate
		}
		if req.DueDate != nil {
			entity.DueDate = req.DueDate
		}
		if req.Status != nil {
			entity.Status = entities.LetterIncomingStatus(*req.Status)
		}
		if err := p.letterRepo.Update(txCtx, entity); err != nil {
			return err
		}
		toStatus := string(entity.Status)
		if p.activity != nil && fromStatus != toStatus {
			userID := appcontext.FromGinContext(txCtx).UserID
			action := "status.changed"
			if err := p.activity.Log(txCtx, id, action, &userID, nil, nil, nil, &fromStatus, &toStatus, map[string]interface{}{}); err != nil {
				return err
			}
		}
		atts, _ := p.attachRepo.ListByLetter(txCtx, id)
		var pr *entities.Priority
		if entity.PriorityID != nil && p.priorityRepo != nil {
			if got, err := p.priorityRepo.Get(txCtx, *entity.PriorityID); err == nil {
				pr = got
			}
		}
		var inst *entities.Institution
		if entity.SenderInstitutionID != nil && p.institutionRepo != nil {
			if got, err := p.institutionRepo.Get(txCtx, *entity.SenderInstitutionID); err == nil {
				inst = got
			}
		}
		out = transformer.LetterEntityToContract(entity, atts, pr, inst)
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (p *LetterProcessorImpl) SoftDeleteIncomingLetter(ctx context.Context, id uuid.UUID) error {
	return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
		if err := p.letterRepo.SoftDelete(txCtx, id); err != nil {
			return err
		}
		if p.activity != nil {
			userID := appcontext.FromGinContext(txCtx).UserID
			action := "letter.deleted"
			if err := p.activity.Log(txCtx, id, action, &userID, nil, nil, nil, nil, nil, map[string]interface{}{}); err != nil {
				return err
			}
		}
		return nil
	})
}

func (p *LetterProcessorImpl) CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error) {
	var out *contract.ListDispositionsResponse
	err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
		userID := appcontext.FromGinContext(txCtx).UserID

		disp := entities.LetterIncomingDisposition{
			LetterID:     req.LetterID,
			DepartmentID: &req.FromDepartment,
			Notes:        req.Notes,
			CreatedBy:    userID,
		}
		if err := p.dispositionRepo.Create(txCtx, &disp); err != nil {
			return err
		}

		var dispDepartments []entities.LetterIncomingDispositionDepartment
		for _, toDept := range req.ToDepartmentIDs {
			dispDepartments = append(dispDepartments, entities.LetterIncomingDispositionDepartment{
				LetterIncomingDispositionID: disp.ID,
				DepartmentID:                toDept,
			})
		}

		if err := p.dispositionDeptRepo.CreateBulk(txCtx, dispDepartments); err != nil {
			return err
		}

		if len(req.SelectedActions) > 0 {
			selections := make([]entities.LetterDispositionActionSelection, 0, len(req.SelectedActions))
			for _, sel := range req.SelectedActions {
				selections = append(selections, entities.LetterDispositionActionSelection{
					DispositionID: disp.ID,
					ActionID:      sel.ActionID,
					Note:          sel.Note,
					CreatedBy:     userID,
				})
			}
			if err := p.dispositionActionSelRepo.CreateBulk(txCtx, selections); err != nil {
				return err
			}
		}

		if p.activity != nil {
			action := "disposition.created"
			ctxMap := map[string]interface{}{"to_department_id": dispDepartments}
			if err := p.activity.Log(txCtx, req.LetterID, action, &userID, nil, nil, &disp.ID, nil, nil, ctxMap); err != nil {
				return err
			}
		}

		out = &contract.ListDispositionsResponse{Dispositions: []contract.DispositionResponse{transformer.DispoToContract(disp)}}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (p *LetterProcessorImpl) ListDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListDispositionsResponse, error) {
	list, err := p.dispositionRepo.ListByLetter(ctx, letterID)
	if err != nil {
		return nil, err
	}
	return &contract.ListDispositionsResponse{Dispositions: transformer.DispositionsToContract(list)}, nil
}

func (p *LetterProcessorImpl) GetEnhancedDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListEnhancedDispositionsResponse, error) {
	// Get dispositions with all related data preloaded in a single query
	dispositions, err := p.dispositionRepo.ListByLetter(ctx, letterID)
	if err != nil {
		return nil, err
	}

	// Get discussions with preloaded user profiles
	discussions, err := p.discussionRepo.ListByLetter(ctx, letterID)
	if err != nil {
		return nil, err
	}

	// Extract all mentioned user IDs from discussions for efficient batch fetching
	var mentionedUserIDs []uuid.UUID
	mentionedUserIDsMap := make(map[uuid.UUID]bool)

	for _, discussion := range discussions {
		if discussion.Mentions != nil {
			mentions := map[string]interface{}(discussion.Mentions)
			if userIDs, ok := mentions["user_ids"]; ok {
				if userIDList, ok := userIDs.([]interface{}); ok {
					for _, userID := range userIDList {
						if userIDStr, ok := userID.(string); ok {
							if userUUID, err := uuid.Parse(userIDStr); err == nil {
								if !mentionedUserIDsMap[userUUID] {
									mentionedUserIDsMap[userUUID] = true
									mentionedUserIDs = append(mentionedUserIDs, userUUID)
								}
							}
						}
					}
				}
			}
		}
	}

	// Fetch all mentioned users in a single batch query
	var mentionedUsers []entities.User
	if len(mentionedUserIDs) > 0 {
		mentionedUsers, err = p.discussionRepo.GetUsersByIDs(ctx, mentionedUserIDs)
		if err != nil {
			return nil, err
		}
	}

	// Transform dispositions
	enhancedDispositions := transformer.EnhancedDispositionsWithPreloadedDataToContract(dispositions)

	// Transform discussions with mentioned users
	enhancedDiscussions := transformer.DiscussionsWithPreloadedDataToContract(discussions, mentionedUsers)

	return &contract.ListEnhancedDispositionsResponse{
		Dispositions: enhancedDispositions,
		Discussions:  enhancedDiscussions,
	}, nil
}

func (p *LetterProcessorImpl) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) {
	var out *contract.LetterDiscussionResponse
	err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
		userID := appcontext.FromGinContext(txCtx).UserID
		mentions := entities.JSONB(nil)
		if req.Mentions != nil {
			mentions = entities.JSONB(req.Mentions)
		}
		disc := &entities.LetterDiscussion{ID: uuid.New(), LetterID: letterID, ParentID: req.ParentID, UserID: userID, Message: req.Message, Mentions: mentions}
		if err := p.discussionRepo.Create(txCtx, disc); err != nil {
			return err
		}
		if p.activity != nil {
			action := "reference_numberdiscussion.created"
			tgt := "discussion"
			ctxMap := map[string]interface{}{"message": req.Message, "parent_id": req.ParentID}
			if err := p.activity.Log(txCtx, letterID, action, &userID, nil, &tgt, &disc.ID, nil, nil, ctxMap); err != nil {
				return err
			}
		}
		out = transformer.DiscussionEntityToContract(disc)
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (p *LetterProcessorImpl) UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) {
	var out *contract.LetterDiscussionResponse
	err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
		disc, err := p.discussionRepo.Get(txCtx, discussionID)
		if err != nil {
			return err
		}
		oldMessage := disc.Message
		disc.Message = req.Message
		if req.Mentions != nil {
			disc.Mentions = entities.JSONB(req.Mentions)
		}
		now := time.Now()
		disc.EditedAt = &now
		if err := p.discussionRepo.Update(txCtx, disc); err != nil {
			return err
		}
		if p.activity != nil {
			userID := appcontext.FromGinContext(txCtx).UserID
			action := "discussion.updated"
			tgt := "discussion"
			ctxMap := map[string]interface{}{"old_message": oldMessage, "new_message": req.Message}
			if err := p.activity.Log(txCtx, letterID, action, &userID, nil, &tgt, &disc.ID, nil, nil, ctxMap); err != nil {
				return err
			}
		}
		out = transformer.DiscussionEntityToContract(disc)
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}
