package services

import (
	"context"
	"fmt"
	"primesys_email/src/clients"
	"primesys_email/src/logger"
	"primesys_email/src/mail"
	"primesys_email/src/templates"
	"primesys_email/src/utils"

	. "primesys_email/src/models"
	"primesys_email/src/models/device"
	"primesys_email/src/models/division"
	. "primesys_email/src/models/report"
	"primesys_email/src/repositories"

	"slices"
	"strconv"
	"strings"
	"time"
)

type SubDivisionReportQueue struct {
	DivisionID string `json:"division_id"`
	DeviceType int    `json:"device_type_id"`
}

const HoursSecond = 7200

// break before sending the email of every sub-division
const subDivisionEmailDelay = 45 * time.Second

// break before executing every queue
const queueDelay = 30 * time.Second

// every report mail has this address in cc
const reportCopyEmail = "report999.primesystrack@gmail.com"

// This will prepare the queue records which division is going to get email
func QueueDailyReports(ctx context.Context) error {
	logger.Info("Queue report running %d", utils.MidnightHourSecond())

	// get all parent divisions which are active now
	divisions, err := repositories.GetAllParentDivisions(ctx)
	if err != nil {
		return err
	}

	// device list of device nos

	// all parent divisions
	for _, division := range divisions {

		logger.Info("--------Queue Daily Reports started--------")
		// all sub-divisions
		subDivisions, err := repositories.GetAllDivisionsByTrackDivisionID(ctx, division.TrackDivisionID)
		if err != nil {
			logger.Error("Division does not exists")
		}

		// collect devices array here
		var divisionDevices []device.DeviceModel
		// all device ids
		var deviceTypeIds []int64

		var subDivisionQueue []SubDivisionReportQueue

		logger.Info("Division: %s", division.Name)

		for _, subDivision := range subDivisions {
			// collect all device nos otherwise deactivate division
			deviceNos := parseDeviceList(subDivision.DeviceList)

			// if there is device nos in division
			if len(deviceNos) > 0 {
				// get all the devices which are out of expiry date
				devices, err := repositories.GetActiveDevices(ctx, deviceNos, division.ID.Hex())
				if err != nil {
					logger.Error("No such division and device exists")
				}
				if len(devices) > 0 {
					subDivisionDeviceTypes := GetDistinctDeviceTypeIDs(devices)

					// sub-division id with each of its device types
					for _, deviceType := range subDivisionDeviceTypes {
						subDivisionQueue = append(subDivisionQueue, SubDivisionReportQueue{
							DivisionID: subDivision.ID.Hex(),
							DeviceType: int(deviceType),
						})
					}

					// unique device types of the whole division
					for _, deviceType := range subDivisionDeviceTypes {
						if !slices.Contains(deviceTypeIds, deviceType) {
							deviceTypeIds = append(deviceTypeIds, deviceType)
						}
					}
					divisionDevices = append(divisionDevices, devices...)
				} else {
					logger.Info("Exipiry division with 0 active devices %s", subDivision.ID)
				}
			}
		}

		logger.Info("Div Queue %v", subDivisionQueue)

		// if no devices in division out of expiry date then deactivate it
		if len(divisionDevices) == 0 && len(deviceTypeIds) == 0 {
			logger.Debug("%s %s Division should be deactivated", division.ID, division.Name)
			deactiveDivisionReport(ctx, division.ID.Hex())
		} else {
			for _, deviceType := range deviceTypeIds {
				deviceImeis := filterDeviceImeis(deviceType, divisionDevices)

				if len(deviceImeis) > 0 {
					endTime, err := repositories.GetEndTimeTripDataByImeis(ctx, deviceImeis)
					if err != nil {
						logger.Error("Trip data error: %v", err)
					}

					logger.Info("Report Date for insert %d", utils.MidnightHourSecond())

					// sub-division ids which have this device type, as ",id1,id2,"
					path := ","
					for _, subQueue := range subDivisionQueue {
						if int64(subQueue.DeviceType) == deviceType {
							path += subQueue.DivisionID + ","
						}
					}

					reportEmailQueue := &ReportEamilQueue{
						DivisionId:         division.ID.Hex(),
						Path:               path,
						DeviceTypeId:       deviceType,
						ReportDate:         utils.MidnightHourSecond(),
						ReportEndTime:      utils.MidnightHourSecond() + endTime,
						ReportExtendedTime: utils.MidnightHourSecond() + endTime + HoursSecond,
						Sent:               false,
						Status:             EmailPending,
						TriggeredBy:        "SYSTEM",
						RetryCount:         0,
						CreatedAt:          utils.CurrentISTEpoch(),
						UpdatedAt:          utils.CurrentISTEpoch(),
					}

					inserted, err := repositories.InsertReportEmailQueue(ctx, reportEmailQueue)
					if err != nil {
						logger.Error("Error not inserted %v", err)
						return err
					}

					if !inserted {
						logger.Debug("Already inserted")
					} else {
						logger.Info("Report email queue inserted for Id: %s division %s : device type: %d status %t", division.ID, division.Name, deviceType, inserted)
					}
				} else {
					logger.Info("No valid device imei found for device type %d division: %s", deviceType, division.ID)
				}

			}
		}
		divisionDevices = divisionDevices[:0]
		logger.Info("===========All Daily Reports are queued=============")
	}
	return nil
}

// parse all device nos in string list
func parseDeviceList(devices string) []int32 {
	var deviceNos []int32

	for value := range strings.SplitSeq(devices, ",") {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}

		deviceNo, err := strconv.ParseInt(value, 10, 32)
		if err != nil {
			continue
		}

		deviceNos = append(deviceNos, int32(deviceNo))
	}

	return deviceNos
}

func GetDistinctDeviceTypeIDs(devices []device.DeviceModel) []int64 {
	distinct := make(map[int64]struct{})

	for _, d := range devices {
		distinct[d.DeviceTypeId] = struct{}{}
	}

	deviceTypeIDs := make([]int64, 0, len(distinct))
	for id := range distinct {
		deviceTypeIDs = append(deviceTypeIDs, id)
	}

	return deviceTypeIDs
}

func deactiveDivisionReport(ctx context.Context, divisionId string) error {
	err := repositories.UpdateReportAndActiveStatus(ctx, divisionId)
	if err != nil {
		logger.Error("Error :%v", err)
		return err
	}
	return nil
}

func filterDeviceImeis(deviceTypeId int64, devices []device.DeviceModel) []int64 {

	if deviceTypeId == 0 {
		return []int64{}
	}

	var deviceImei []int64

	for _, val := range devices {
		if deviceTypeId == val.DeviceTypeId {
			deviceImei = append(deviceImei, val.DeviceImei)
		}
	}
	return deviceImei
}

func DivisionEmailDelivery(ctx context.Context, url string, sender *mail.DynamicMailSender) error {

	startTime := utils.MidnightHourSecond()
	endTime := utils.CurrentISTEpoch()

	logger.Info("start time %d %d", startTime, endTime)

	client := clients.NewExceptionReportClient(url)

	queues, err := repositories.GetUnsentReportEmailQueue(ctx, startTime, endTime)

	logger.Info("total div %d", len(queues))

	if err != nil {
		logger.Error("Email queue error : %v", err)
	}

	for _, q := range queues {
		time.Sleep(queueDelay)

		logger.Info("div %s que %s", q.ID, q.DivisionId)
		updateQueueStatus(ctx, q, deliverQueue(ctx, client, sender, q))
	}

	return nil
}

// deliverQueue sends the reports of one queue entry: every sub-division of the queue path first,
// then the parent division. Returns an error when the parent division report is not sent.
// Every failing step is saved in report_email_queue_log with its real error.
func deliverQueue(ctx context.Context, client *clients.ExceptionReportClient, sender *mail.DynamicMailSender, q ReportEamilQueue) error {

	m := newQueueMailer(q, sender)

	reportLog, step, err := getQueueReportLog(ctx, client, q)
	if err != nil {
		m.logFailure(ctx, q.DivisionId, "", step, err)
		return err
	}

	divisionLogin, err := m.loadSender(ctx, q.DivisionId)
	if err != nil {
		m.logFailure(ctx, q.DivisionId, "", LoadSender, err)
		return err
	}

	if err := repositories.MarkReportEmailQueueProcessing(ctx, q.ID); err != nil {
		logger.Error("Queue processing update failed %s : %v", q.ID.Hex(), err)
	}

	sendSubDivisionReports(ctx, m, q)

	// after the sub-divisions, the parent division gets its report with the count report
	return m.logSend(ctx, divisionLogin, SendParentEmail, func() (string, error) {
		return m.sendParentReport(divisionLogin, reportLog)
	})
}

// updateQueueStatus marks the queue entry sent, or failed with the error (retried by the next run)
func updateQueueStatus(ctx context.Context, q ReportEamilQueue, deliveryErr error) {

	queueId := q.ID.Hex()

	if deliveryErr != nil {
		logger.Error("Queue delivery failed %s : %v", queueId, deliveryErr)

		if err := repositories.MarkReportEmailQueueFailed(ctx, q.ID, deliveryErr.Error()); err != nil {
			logger.Error("Queue failed update failed %s : %v", queueId, err)
		}
		return
	}

	if err := repositories.MarkReportEmailQueueSent(ctx, q.ID); err != nil {
		logger.Error("Queue sent update failed %s : %v", queueId, err)
	}
}

// getQueueReportLog returns the report of the queue division, regenerated when it is not generated yet.
// On error it also returns the step which failed.
func getQueueReportLog(ctx context.Context, client *clients.ExceptionReportClient, q ReportEamilQueue) (*division.DivisionReportLog, ProcessType, error) {

	queueId := q.ID.Hex()
	deviceType := int(q.DeviceTypeId)
	info := reportInfo(queueId, q.DivisionId, "", deviceType, q.ReportDate)

	reportLog, err := repositories.GetDivisionReportLog(ctx, q.DivisionId, int32(q.DeviceTypeId), q.ReportDate)
	if err != nil {
		logger.Error("Division Report fetch error %s : %v", info, err)
		return nil, FetchReport, fmt.Errorf("report fetch error: %w", err)
	}

	if reportGenerated(reportLog) {
		logger.Info("Report Generated of %s %d %d", q.DivisionId, q.DeviceTypeId, q.ReportDate)
		return reportLog, "", nil
	}

	logger.Error("Report not generated %s %d %d", q.DivisionId, q.DeviceTypeId, q.ReportDate)
	if err := regenerateReport(ctx, client, queueId, q.DivisionId, deviceType, q.ReportDate, info); err != nil {
		return nil, RegenerateReport, err
	}

	reportLog, err = fetchRegeneratedReport(ctx, q.DivisionId, deviceType, q.ReportDate, info)
	if err != nil {
		return nil, RegenerateReport, err
	}

	return reportLog, "", nil
}

// newQueueMailer returns the mailer of the queue entry, its sender account is set by loadSender
func newQueueMailer(q ReportEamilQueue, sender *mail.DynamicMailSender) *reportMailer {
	return &reportMailer{
		queueId:    q.ID.Hex(),
		userId:     q.TriggeredBy,
		sender:     sender,
		deviceType: int(q.DeviceTypeId),
		reportDate: q.ReportDate,
	}
}

// loadSender sets the sender account of the parent division (report_email_id is the email of
// the email master) and returns the parent division
func (m *reportMailer) loadSender(ctx context.Context, divisionId string) (*division.DivisionLoginModel, error) {

	divisionLogin, err := repositories.GetDivisionLoginByID(ctx, divisionId)
	if err != nil {
		logger.Error("Division login not found %s : %v", divisionId, err)
		return nil, fmt.Errorf("division login not found: %w", err)
	}

	email, err := getEmailMaster(ctx, divisionLogin.ReportEmailId)
	if err != nil {
		logger.Error("Email master error div: %s : %v", divisionLogin.Name, err)
		return nil, fmt.Errorf("email master error: %w", err)
	}

	m.email = email
	return divisionLogin, nil
}

// sendSubDivisionReports sends the report of every sub-division of the queue path (",id1,id2,")
func sendSubDivisionReports(ctx context.Context, m *reportMailer, q ReportEamilQueue) {
	for _, pathId := range utils.SplitPath(q.Path) {
		sendSubDivisionReport(ctx, m, pathId)
	}
}

// sendSubDivisionReport sends the generated report of the sub-division:
// To is the sub-division, cc is every division of its path
func sendSubDivisionReport(ctx context.Context, m *reportMailer, subDivisionId string) {

	// the report of the day is mailed once: skip when the log says it is already sent
	sent, err := repositories.IsReportEmailSent(ctx, subDivisionId, m.deviceType, m.reportDate, SendChildEmail)
	if err != nil {
		logger.Error("Sent log check error %s, report not sent : %v", subDivisionId, err)
		m.logFailure(ctx, subDivisionId, "", PrepareChildEmail, fmt.Errorf("sent log check error: %w", err))
		return
	}

	if sent {
		logger.Info("Report already sent, skipped sub-division: %s device type: %d report date: %d", subDivisionId, m.deviceType, m.reportDate)
		return
	}

	reportLog, err := repositories.GetDivisionReportLog(ctx, subDivisionId, int32(m.deviceType), m.reportDate)
	if err != nil {
		logger.Error("Sub-Division report fetch error %s : %v", subDivisionId, err)
		m.logFailure(ctx, subDivisionId, "", PrepareChildEmail, fmt.Errorf("report fetch error: %w", err))
		return
	}

	if !reportGenerated(reportLog) {
		m.logFailure(ctx, subDivisionId, "", PrepareChildEmail, fmt.Errorf("report not generated"))
		return
	}

	subDivision, err := repositories.GetSubDivisionLoginByID(ctx, subDivisionId)
	if err != nil {
		logger.Error("Sub-Division login not found %s : %v", subDivisionId, err)
		m.logFailure(ctx, subDivisionId, "", PrepareChildEmail, fmt.Errorf("sub-division login not found: %w", err))
		return
	}

	ccEmails, err := getPathCcEmails(ctx, subDivision)
	if err != nil {
		logger.Error("Path divisions fetch error %s : %v", subDivision.Path, err)
		m.logFailure(ctx, subDivisionId, subDivision.Name, PrepareChildEmail, fmt.Errorf("path divisions fetch error: %w", err))
		return
	}

	time.Sleep(subDivisionEmailDelay)

	m.logSend(ctx, subDivision, SendChildEmail, func() (string, error) {
		return m.sendReportLog(subDivision, reportLog, ccEmails)
	})
}

// getPathCcEmails returns the usernames of the divisions of the sub-division path
// (",rootId,parentId,currentId,") in path order, without the sub-division itself
func getPathCcEmails(ctx context.Context, subDivision *division.DivisionLoginModel) ([]string, error) {

	usernames, err := repositories.GetActiveUsernamesByIDs(ctx, utils.SplitPath(subDivision.Path))
	if err != nil {
		return nil, err
	}

	var ccEmails []string
	for _, username := range usernames {
		if username != subDivision.Username {
			ccEmails = appendEmail(ccEmails, username)
		}
	}

	return ccEmails, nil
}

// getEmailMaster returns the active email master account with this email
func getEmailMaster(ctx context.Context, reportEmailId string) (EmailMasterModel, error) {

	if strings.TrimSpace(reportEmailId) == "" {
		return EmailMasterModel{}, fmt.Errorf("division has no report_email_id")
	}

	email, err := repositories.GetByEmail(ctx, reportEmailId)
	if err != nil {
		return EmailMasterModel{}, err
	}

	if email == nil {
		return EmailMasterModel{}, fmt.Errorf("email master %s not found", reportEmailId)
	}

	if !email.ActiveStatus {
		return EmailMasterModel{}, fmt.Errorf("email master %s is not active", reportEmailId)
	}

	if strings.TrimSpace(email.Email) == "" || strings.TrimSpace(email.Password) == "" {
		return EmailMasterModel{}, fmt.Errorf("email master %s username or password is empty", reportEmailId)
	}

	return *email, nil
}

// reportMailer sends the report mails of one queue entry
type reportMailer struct {
	queueId    string
	userId     string
	email      EmailMasterModel
	sender     *mail.DynamicMailSender
	deviceType int
	reportDate int64
}

// sendReportLog builds the excel of the report log and mails it: To is the division,
// the parents (and the report copy address) get the section count report too
func (m *reportMailer) sendReportLog(d *division.DivisionLoginModel, reportLog *division.DivisionReportLog, parentEmails []string) (string, error) {

	divisionId := d.ID.Hex()
	to := d.Username
	info := reportInfo(m.queueId, divisionId, d.Name, m.deviceType, m.reportDate)

	if strings.TrimSpace(to) == "" {
		logger.Warn("No email for division, report not sent %s", info)
		return "", fmt.Errorf("division %s has no email", d.Name)
	}

	reportBytes, err := getExcelReport(reportLog.Reports, m.deviceType, divisionId, d.Name, m.reportDate)
	if err != nil {
		logger.Error("Excel report error %s : %v", info, err)
		return "", err
	}

	date := utils.EpochToDateString(m.reportDate)
	fileDate := strings.ReplaceAll(date, "/", "-")
	subject := fmt.Sprintf("%s Report %s", d.Name, date)
	body := fmt.Sprintf("Please find attached the report of %s for %s.", d.Name, date)

	attachments := []mail.Attachment{
		{FileName: fmt.Sprintf("%s_%s.xlsx", d.Name, fileDate), Data: reportBytes},
	}

	// one mail: To is the division, cc is every division above it and the report copy address
	cc := appendEmail(parentEmails, reportCopyEmail)

	return m.sendMail(info, to, cc, subject, body, attachments)
}

// sendParentReport mails the parent division its own report with the section count report,
// To is the parent division, cc is the report copy address
func (m *reportMailer) sendParentReport(d *division.DivisionLoginModel, reportLog *division.DivisionReportLog) (string, error) {

	divisionId := d.ID.Hex()
	to := d.Username
	info := reportInfo(m.queueId, divisionId, d.Name, m.deviceType, m.reportDate)

	if strings.TrimSpace(to) == "" {
		logger.Warn("No email for parent division, report not sent %s", info)
		return "", fmt.Errorf("parent division %s has no email", d.Name)
	}

	reportBytes, err := getExcelReport(reportLog.Reports, m.deviceType, divisionId, d.Name, m.reportDate)
	if err != nil {
		logger.Error("Excel report error %s : %v", info, err)
		return "", err
	}

	date := utils.EpochToDateString(m.reportDate)
	fileDate := strings.ReplaceAll(date, "/", "-")
	subject := fmt.Sprintf("%s Report %s", d.Name, date)
	body := fmt.Sprintf("Please find attached the report of %s for %s.", d.Name, date)

	attachments := []mail.Attachment{
		{FileName: fmt.Sprintf("%s_%s.xlsx", d.Name, fileDate), Data: reportBytes},
	}

	// section count report, nil for the device types which have none
	countBytes, err := getCountExcelReport(reportLog.Reports, d.Name, divisionId, m.deviceType, m.reportDate)
	if err != nil {
		logger.Error("Count report error, mail sent without it %s : %v", info, err)
	} else if countBytes != nil {
		attachments = append(attachments, mail.Attachment{
			FileName: fmt.Sprintf("%s_Section_Count_%s.xlsx", d.Name, fileDate),
			Data:     countBytes,
		})
	}

	return m.sendMail(info, to, []string{reportCopyEmail}, subject, body, attachments)
}

// sendMail sends one report mail, no bcc. Returns the addresses of the mail (to and cc).
func (m *reportMailer) sendMail(info string, to string, cc []string, subject string, body string, attachments []mail.Attachment) (string, error) {
	sentTo := strings.Join(append([]string{to}, cc...), ",")

	if err := m.sender.SendMailWithAttachments(m.email.Email, m.email.Password, to, cc, nil, subject, body, attachments); err != nil {
		logger.Error("Mail failed %s to: %s : %v", info, to, err)
		return sentTo, err
	}

	logger.Info("Mail sent %s to: %s cc: %v attachments: %d", info, to, cc, len(attachments))
	return sentTo, nil
}

// logFailure saves a FAILED report_email_queue_log entry for a step before the mail,
// with the real error, so it can be seen why a division did not get its report
func (m *reportMailer) logFailure(ctx context.Context, divisionId string, divisionName string, processType ProcessType, failure error) {

	now := utils.CurrentISTEpoch()

	_, err := repositories.InsertReportEmailQueueLog(ctx, &ReportEmailQueueLog{
		QueueId:      m.queueId,
		DivisionId:   divisionId,
		DivisionName: divisionName,
		ProcessType:  processType,
		UserId:       m.userId,
		DeviceTypeId: m.deviceType,
		ReportDate:   m.reportDate,
		Status:       EmailFailed,
		ErrorMessage: failure.Error(),
		StartedAt:    now,
		FinishedAt:   now,
	})
	if err != nil {
		logger.Error("Queue failure log insert failed queue: %s division: %s : %v", m.queueId, divisionId, err)
	}
}

// logSend saves a report_email_queue_log entry for the division mail: SENDING when it starts,
// then SENT or FAILED with the addresses and the error. Returns the error of send.
func (m *reportMailer) logSend(ctx context.Context, d *division.DivisionLoginModel, processType ProcessType,
	send func() (string, error)) error {

	logId, logErr := repositories.InsertReportEmailQueueLog(ctx, &ReportEmailQueueLog{
		QueueId:      m.queueId,
		DivisionId:   d.ID.Hex(),
		DivisionName: d.Name,
		ProcessType:  processType,
		UserId:       m.userId,
		DeviceTypeId: m.deviceType,
		ReportDate:   m.reportDate,
		Status:       EmailSending,
		StartedAt:    utils.CurrentISTEpoch(),
	})
	if logErr != nil {
		logger.Error("Queue log insert failed queue: %s division: %s : %v", m.queueId, d.Name, logErr)
	}

	sentTo, err := send()

	// the mail is sent even when the log could not be inserted
	if logErr != nil {
		return err
	}

	status, errMsg := EmailSent, ""
	if err != nil {
		status, errMsg = EmailFailed, err.Error()
	}

	if finishErr := repositories.FinishReportEmailQueueLog(ctx, logId, status, sentTo, errMsg); finishErr != nil {
		logger.Error("Queue log update failed %s : %v", logId.Hex(), finishErr)
	}

	return err
}

// reportGenerated is true when the ACTIVE report log exists and has device reports
func reportGenerated(reportLog *division.DivisionReportLog) bool {
	return reportLog != nil && len(reportLog.Reports) > 0
}

// fetchRegeneratedReport fetches the report again after a successful regenerate.
// Returns an error when it is still not generated.
func fetchRegeneratedReport(ctx context.Context, divisionId string, deviceType int, reportDate int64, info string) (*division.DivisionReportLog, error) {

	reportLog, err := repositories.GetDivisionReportLog(ctx, divisionId, int32(deviceType), reportDate)
	if err != nil {
		logger.Error("Report fetch after regenerate error %s : %v", info, err)
		return nil, fmt.Errorf("report fetch after regenerate error: %w", err)
	}

	if !reportGenerated(reportLog) {
		logger.Error("Report still not generated after regenerate, not sent %s", info)
		return nil, fmt.Errorf("report still not generated after regenerate")
	}

	logger.Info("Report found after regenerate %s", info)
	return reportLog, nil
}

// regenerateReport asks the report API to generate the report and logs the exact request.
// triggeredBy is the log info of the division whose report was missing.
func regenerateReport(ctx context.Context, client *clients.ExceptionReportClient, queueId string, divisionId string,
	deviceType int, reportDate int64, triggeredBy string) error {

	req := clients.EmailReportRegenerateRequest{
		DivisionID: divisionId,
		DeviceType: deviceType,
		DateTime:   int(reportDate),
		QueueId:    queueId,
	}

	request := fmt.Sprintf("divisionId=%s deviceType=%d startDateTime=%d (%s) queueId=%s",
		req.DivisionID, req.DeviceType, req.DateTime, utils.EpochToDateString(reportDate), req.QueueId)

	logger.Warn("Regenerate request %s, missing report of %s", request, triggeredBy)

	if err := client.RegenerateReport(ctx, req); err != nil {
		logger.Error("Regenerate request failed %s : %v", request, err)
		return fmt.Errorf("regenerate request failed: %w", err)
	}

	logger.Info("Regenerate completed %s", request)
	return nil
}

// reportInfo is the same queue / division / type / date details on every log line,
// so a queue entry can be traced in the log file
func reportInfo(queueId string, divisionId string, divisionName string, deviceType int, reportDate int64) string {
	return fmt.Sprintf("queue: %s div: %s (%s) type: %d date: %s (%d)",
		queueId, divisionId, divisionName, deviceType, utils.EpochToDateString(reportDate), reportDate)
}

// appendEmail returns a new slice with the email added, blank emails are skipped
func appendEmail(emails []string, email string) []string {
	result := make([]string, 0, len(emails)+1)
	result = append(result, emails...)
	if strings.TrimSpace(email) != "" {
		result = append(result, email)
	}
	return result
}

// SWM LJN division has its own SWM report layout
const swmLJNDivisionId = "68b9290446c12a561f41c735"

func getExcelReport(reports []DivisionTripReportModel, deviceType int, divisionId string, divisionName string, reportDate int64) ([]byte, error) {

	logger.Info("entered to create excel report %d, %d", len(reports), deviceType)

	dateStr := utils.EpochToDateString(reportDate)

	var reportBytes []byte
	var err error

	switch deviceType {
	case 1:
		logger.Info("keyman report excel %d %s %d", len(reports), divisionName, reportDate)
		keyman := &templates.KeymanReportExcel{}
		reportBytes, err = keyman.CreateKeyManReportExcel(reports, divisionName, dateStr)
	case 2:
		patrolman := &templates.PatrolmanReportExcel{}
		reportBytes, err = patrolman.CreatePatrolmanReportExcel(reports, divisionName, dateStr)
	case 3:
		usfd := &templates.UsfdReportExcel{}
		reportBytes, err = usfd.CreateUsfdReportExcel(reports, divisionName, "USFD Report", dateStr)
	case 12:
		pwi := &templates.UsfdReportExcel{}
		reportBytes, err = pwi.CreateUsfdReportExcel(reports, divisionName, "PWI Report", dateStr)
	case 14:
		trackMachine := &templates.UsfdReportExcel{}
		reportBytes, err = trackMachine.CreateUsfdReportExcel(reports, divisionName, "Track Machine Report", dateStr)
	case 4:
		reportBytes, err = templates.CreateMateReportExcel(reports, divisionName, dateStr)
	case 5:
		reportBytes, err = templates.CreateGateMitraReportExcel(reports, divisionName, dateStr)
	case 6:
		if divisionId == swmLJNDivisionId {
			reportBytes, err = templates.CreateSwmLJNReportExcel(reports, divisionName, dateStr)
			break
		}
		reportBytes, err = templates.CreateSwmReportExcel(reports, divisionName, dateStr)
	case 7:
		reportBytes, err = templates.CreateTRDPatrolmanReportExcel(reports, divisionName, dateStr)
	case 8:
		reportBytes, err = templates.CreateKeyManTripWiseReportExcel(reports, divisionName, "Morning Keyman Report", dateStr)
	case 9:
		reportBytes, err = templates.CreateKeyManTripWiseReportExcel(reports, divisionName, "Summer Patrolman Report", dateStr)
	case 10:
		reportBytes, err = templates.CreateMopperReportExcel(reports, divisionName, dateStr)
	case 13:
		reportBytes, err = templates.CreatePushTrolleyReportExcel(reports, divisionName, dateStr)
	case 20:
		reportBytes, err = templates.CreateKeyManMidShiftReportExcel(reports, divisionName, dateStr)
	default:
		return nil, fmt.Errorf("No such Template exists for Device Type: %d, Division name %s", deviceType, divisionName)
	}

	if err != nil {
		return nil, fmt.Errorf("Error for device type %d report, Division: %s err: %w", deviceType, divisionName, err)
	}

	return reportBytes, nil
}

// getCountExcelReport builds the section count report. Returns nil bytes without error for the
// device types which have no count report.
func getCountExcelReport(reports []DivisionTripReportModel, divisionName string, divisionId string, deviceType int, reportDate int64) ([]byte, error) {

	switch deviceType {
	// 6 is the same count report for SWM LJN and other SWM divisions
	case 1, 2, 6, 7, 8, 9, 20:
		sectionCounts := templates.BuildSectionCounts(reports)

		countReport := &templates.SectionCountReportExcel{}
		countBytes, err := countReport.CreateSectionCountReportExcel(sectionCounts, divisionName, utils.EpochToDateString(reportDate))
		if err != nil {
			return nil, fmt.Errorf("Error for Section Count report Division: %s (%s) Device Type: %d err: %w", divisionName, divisionId, deviceType, err)
		}
		return countBytes, nil
	}

	// 3, 4, 5, 10, 13 have no count report
	return nil, nil
}
