package service

import (
	"context"
	"errors"
	"path/filepath"
	"strings"
	"time"

	filemanager "wucher/internal/domain/file_manager"
	helicopterfile "wucher/internal/domain/helicopter_file"
	helicopterfilerealtime "wucher/internal/realtime/helicopterfile"
	"wucher/internal/shared/pkg/attachmentresolver"
	"wucher/internal/shared/pkg/uuidv7"
)

type HelicopterFileService struct {
	repo        helicopterfile.Repository
	fileManager filemanager.Service
	events      helicopterFileEventPublisher
}

func NewHelicopterFileService(repo helicopterfile.Repository) *HelicopterFileService {
	return &HelicopterFileService{repo: repo}
}

type helicopterFileEventPublisher interface {
	PublishToUsers(userUUIDs []string, event helicopterfilerealtime.Event) int
}

func (s *HelicopterFileService) WithFileManagerService(fileManagerSvc filemanager.Service) *HelicopterFileService {
	s.fileManager = fileManagerSvc
	return s
}

func (s *HelicopterFileService) WithHelicopterFileEvents(publisher helicopterFileEventPublisher) *HelicopterFileService {
	s.events = publisher
	return s
}

func (s *HelicopterFileService) Create(ctx context.Context, row *helicopterfile.HelicopterFile) error {
	if row != nil && row.Position <= 0 {
		next, err := s.nextPositionByHelicopterSection(ctx, row.HelicopterID, row.Section)
		if err != nil {
			return err
		}
		row.Position = next
	}
	return s.repo.Create(ctx, row)
}

func (s *HelicopterFileService) Update(ctx context.Context, row *helicopterfile.HelicopterFile) error {
	return s.repo.Update(ctx, row)
}

func (s *HelicopterFileService) Delete(ctx context.Context, id []byte) error {
	return s.repo.Delete(ctx, id)
}

func (s *HelicopterFileService) GetByID(ctx context.Context, id []byte) (*helicopterfile.HelicopterFile, error) {
	return s.repo.GetByID(ctx, id)
}

func (s *HelicopterFileService) List(ctx context.Context, filter string, sort string, limit, offset int) ([]helicopterfile.HelicopterFile, int64, error) {
	return s.repo.List(ctx, filter, normalizeHelicopterFileSort(sort), limit, offset)
}

func (s *HelicopterFileService) ListByHelicopter(ctx context.Context, helicopterID []byte) ([]helicopterfile.HelicopterFile, error) {
	return s.repo.ListByHelicopter(ctx, helicopterID)
}

func (s *HelicopterFileService) ListByHelicopterAndSection(ctx context.Context, helicopterID []byte, section string) ([]helicopterfile.HelicopterFile, error) {
	return s.repo.ListByHelicopterAndSection(ctx, helicopterID, section)
}

func (s *HelicopterFileService) HelicopterExists(ctx context.Context, helicopterID []byte) (bool, error) {
	return s.repo.HelicopterExists(ctx, helicopterID)
}

func (s *HelicopterFileService) AttachmentExists(ctx context.Context, attachmentID []byte) (bool, error) {
	return s.repo.AttachmentExists(ctx, attachmentID)
}

func (s *HelicopterFileService) RequestUploadIntentsBulk(ctx context.Context, inputs []helicopterfile.UploadIntentBulkInput, actorID []byte) []helicopterfile.UploadIntentBulkItemResult {
	items := make([]helicopterfile.UploadIntentBulkItemResult, len(inputs))
	if s.fileManager == nil {
		for i := range items {
			items[i] = helicopterfile.UploadIntentBulkItemResult{
				Index:   i,
				Success: false,
				Error: &helicopterfile.BulkItemError{
					Status: 400,
					Title:  "Upload intent failed",
					Detail: "file manager service is not configured",
				},
			}
		}
		return items
	}

	for i := range inputs {
		item := inputs[i]
		typ := strings.TrimSpace(item.Type)
		if typ != "helicopter_file_upload" {
			items[i] = uploadIntentBulkError(i, 422, "Validation error", "type must be helicopter_file_upload")
			continue
		}

		name := strings.TrimSpace(item.Name)
		if name == "" {
			items[i] = uploadIntentBulkError(i, 422, "Validation error", "name is required")
			continue
		}

		contentType := strings.TrimSpace(item.ContentType)
		if contentType == "" {
			items[i] = uploadIntentBulkError(i, 422, "Validation error", "content_type is required")
			continue
		}

		if item.SizeBytes < 0 {
			items[i] = uploadIntentBulkError(i, 422, "Validation error", "size_bytes must be greater than or equal to 0")
			continue
		}

		grant, grantErr := s.fileManager.RequestFileUploadIntent(ctx, filemanager.RequestFileUploadIntentParams{
			Name:        name,
			ContentType: contentType,
			SizeBytes:   item.SizeBytes,
			ActorID:     actorID,
		})
		if grantErr != nil {
			items[i] = uploadIntentBulkError(i, 400, "Upload intent failed", grantErr.Error())
			continue
		}

		items[i] = helicopterfile.UploadIntentBulkItemResult{
			Index:   i,
			Success: true,
			Grant:   grant,
		}
	}

	return items
}

func (s *HelicopterFileService) CreateFromUploadIntentsBulk(ctx context.Context, inputs []helicopterfile.CreateBulkInput, actorID []byte) []helicopterfile.CreateBulkItemResult {
	items := make([]helicopterfile.CreateBulkItemResult, len(inputs))
	if s.fileManager == nil {
		for i := range items {
			items[i] = createBulkError(i, 400, "Create failed", "file manager service is not configured")
		}
		return items
	}

	for i := range inputs {
		item := inputs[i]
		typ := strings.TrimSpace(item.Type)
		if typ != "helicopter_file_create" && typ != "helicopter_file" {
			items[i] = createBulkError(i, 422, "Validation error", "type must be helicopter_file_create or helicopter_file")
			continue
		}

		section := strings.TrimSpace(item.Section)
		if !isValidHelicopterFileSection(section) {
			items[i] = createBulkError(i, 422, "Validation error", "section is invalid")
			continue
		}

		helicopterID, err := uuidv7.ParseString(strings.TrimSpace(item.HelicopterID))
		if err != nil {
			items[i] = createBulkError(i, 422, "Validation error", "helicopter_id is invalid UUID")
			continue
		}
		exists, err := s.repo.HelicopterExists(ctx, helicopterID)
		if err != nil {
			items[i] = createBulkError(i, 400, "Create failed", err.Error())
			continue
		}
		if !exists {
			items[i] = createBulkError(i, 422, "Validation error", "helicopter_id not found")
			continue
		}

		uploadIntentID, parseErr := uuidv7.ParseString(strings.TrimSpace(item.UploadIntentUUID))
		if parseErr != nil {
			items[i] = createBulkError(i, 422, "Validation error", "upload_intent_uuid is invalid UUID")
			continue
		}

		fileName := strings.TrimSpace(item.FileName)
		if fileName == "" {
			items[i] = createBulkError(i, 422, "Validation error", "file_name is required")
			continue
		}

		var folderID []byte
		folderProvided := false
		if item.FolderUUID != nil {
			folderProvided = true
			folderUUID := strings.TrimSpace(*item.FolderUUID)
			if folderUUID != "" {
				parsedFolderID, folderErr := uuidv7.ParseString(folderUUID)
				if folderErr != nil {
					items[i] = createBulkError(i, 422, "Validation error", "folder_uuid is invalid UUID")
					continue
				}
				folderID = parsedFolderID
			}
		}

		createdFile, createErr := s.fileManager.CreateFileNodeFromUploadIntent(ctx, filemanager.CreateFileFromUploadIntentParams{
			UploadIntentID: uploadIntentID,
			FolderID:       folderID,
			FolderProvided: folderProvided,
			Name:           fileName,
			ActorID:        actorID,
			IsTemplate:     true,
		})
		if createErr != nil {
			if errors.Is(createErr, filemanager.ErrDuplicateName) {
				targetFolderID := folderID
				if !folderProvided || len(targetFolderID) == 0 {
					root, rootErr := s.fileManager.GetOrCreateSystemRootFolder(ctx)
					if rootErr != nil || root == nil {
						detail := "unable to resolve target folder"
						if rootErr != nil {
							detail = rootErr.Error()
						}
						items[i] = createBulkError(i, 400, "Create failed", detail)
						continue
					}
					targetFolderID = root.ID
				}

				nameNormalized := strings.ToLower(filepath.Base(strings.TrimSpace(fileName)))
				if nameNormalized == "" {
					items[i] = createBulkError(i, 422, "Validation error", "file_name is invalid")
					continue
				}
				existingFile, existingErr := s.fileManager.GetFileByFolderAndName(ctx, targetFolderID, nameNormalized)
				if existingErr != nil || existingFile == nil {
					detail := "duplicate file exists but cannot be resolved"
					if existingErr != nil {
						detail = existingErr.Error()
					}
					items[i] = createBulkError(i, 400, "Create failed", detail)
					continue
				}
				createdFile = existingFile
			} else {
				items[i] = createBulkError(i, 400, "Create failed", createErr.Error())
				continue
			}
		}
		if createdFile == nil {
			items[i] = createBulkError(i, 400, "Create failed", "unable to create file from upload intent")
			continue
		}

		position := 0
		if item.Position != nil {
			position = *item.Position
		}
		isMandatory := false
		if item.IsMandatory != nil {
			isMandatory = *item.IsMandatory
		}
		row := &helicopterfile.HelicopterFile{
			Section:      section,
			Position:     position,
			IsMandatory:  isMandatory,
			HelicopterID: helicopterID,
			SourceFileID: append([]byte(nil), createdFile.ID...),
			CreatedBy:    actorID,
			UpdatedBy:    actorID,
		}
		if row.Position <= 0 {
			next, nextErr := s.nextPositionByHelicopterSection(ctx, row.HelicopterID, row.Section)
			if nextErr != nil {
				items[i] = createBulkError(i, 400, "Create failed", nextErr.Error())
				continue
			}
			row.Position = next
		}
		if err := s.repo.Create(ctx, row); err != nil {
			items[i] = createBulkError(i, 400, "Create failed", err.Error())
			continue
		}

		createdRow, getErr := s.repo.GetByID(ctx, row.ID)
		if getErr == nil && createdRow != nil {
			items[i] = helicopterfile.CreateBulkItemResult{
				Index:   i,
				Success: true,
				Row:     createdRow,
			}
			continue
		}

		items[i] = helicopterfile.CreateBulkItemResult{
			Index:   i,
			Success: true,
			Row:     row,
		}
	}

	return items
}

func (s *HelicopterFileService) FinalizePendingAttachments(ctx context.Context, limit int) (int, error) {
	if s == nil || s.repo == nil {
		return 0, errors.New("helicopter file service is not configured")
	}
	if s.fileManager == nil {
		return 0, nil
	}

	rows, err := s.repo.ListPendingAttachments(ctx, limit)
	if err != nil {
		return 0, err
	}
	return s.finalizePendingAttachmentRows(ctx, rows), nil
}

func (s *HelicopterFileService) FinalizePendingAttachmentsBySourceFileUUID(ctx context.Context, sourceFileUUID string, limit int) (int, error) {
	if s == nil || s.repo == nil {
		return 0, errors.New("helicopter file service is not configured")
	}
	if s.fileManager == nil {
		return 0, nil
	}

	sourceFileUUID = strings.TrimSpace(sourceFileUUID)
	if sourceFileUUID == "" {
		return 0, nil
	}
	sourceFileID, err := uuidv7.ParseString(sourceFileUUID)
	if err != nil {
		return 0, err
	}

	rows, err := s.repo.ListPendingAttachmentsBySourceFileID(ctx, sourceFileID, limit)
	if err != nil {
		return 0, err
	}
	return s.finalizePendingAttachmentRows(ctx, rows), nil
}

func (s *HelicopterFileService) finalizePendingAttachmentRows(ctx context.Context, rows []helicopterfile.HelicopterFile) int {
	finalized := 0
	for i := range rows {
		row := &rows[i]
		if len(row.ID) != 16 || len(row.SourceFileID) != 16 || len(row.FileAttachmentID) == 16 {
			continue
		}

		attachmentID, ok, err := s.finalizePendingAttachment(ctx, row)
		if err != nil || !ok || len(attachmentID) != 16 {
			continue
		}
		finalized++
	}
	return finalized
}

func (s *HelicopterFileService) finalizePendingAttachment(ctx context.Context, row *helicopterfile.HelicopterFile) ([]byte, bool, error) {
	if s == nil || row == nil || s.fileManager == nil {
		return nil, false, nil
	}
	if len(row.SourceFileID) != 16 || len(row.ID) != 16 || len(row.HelicopterID) != 16 {
		return nil, false, nil
	}

	sourceFileUUID := uuidBytesToStringOrEmpty(row.SourceFileID)
	if sourceFileUUID == "" {
		return nil, false, nil
	}
	helicopterUUID := uuidBytesToStringOrEmpty(row.HelicopterID)
	if helicopterUUID == "" {
		return nil, false, nil
	}
	category := "helicopter_file"
	attachmentID, err := attachmentresolver.Resolve(ctx, attachmentresolver.Params{
		FileManager: s.fileManager,
		RawFileID:   &sourceFileUUID,

		AttachmentField: "file_attachment_id",
		FileField:       "source_file_id",
		RequireValue:    true,

		RefType:  "helicopter",
		RefID:    helicopterUUID,
		Category: &category,

		IsPrimary: false,
		ActorID:   append([]byte(nil), row.UpdatedBy...),
	})
	if err != nil {
		var resolveErr *attachmentresolver.ResolveError
		if errors.As(err, &resolveErr) && resolveErr.Code == attachmentresolver.ErrorFileNotReadyForAttachment {
			return nil, false, nil
		}
		return nil, false, err
	}
	if len(attachmentID) != 16 {
		return nil, false, nil
	}

	row.FileAttachmentID = append([]byte(nil), attachmentID...)
	row.SourceFileID = nil
	row.UpdatedAt = time.Now().UTC()
	if len(row.UpdatedBy) == 0 {
		row.UpdatedBy = append([]byte(nil), row.CreatedBy...)
	}
	if err := s.repo.Update(ctx, row); err != nil {
		return nil, false, err
	}
	s.publishHelicopterFileState(row, attachmentID)
	if err := s.markAttachmentFileActive(ctx, attachmentID, row.UpdatedBy); err != nil {
		return attachmentID, true, err
	}
	return attachmentID, true, nil
}

func (s *HelicopterFileService) publishHelicopterFileState(row *helicopterfile.HelicopterFile, fileID []byte) {
	if s == nil || s.events == nil || row == nil || len(row.ID) != 16 || len(row.HelicopterID) != 16 {
		return
	}

	actorID := row.UpdatedBy
	if len(actorID) != 16 {
		actorID = row.CreatedBy
	}
	if len(actorID) != 16 {
		return
	}

	actorUUID := uuidBytesToStringOrEmpty(actorID)
	helicopterUUID := uuidBytesToStringOrEmpty(row.HelicopterID)
	helicopterFileUUID := uuidBytesToStringOrEmpty(row.ID)
	attachmentUUID := uuidBytesToStringOrEmpty(fileID)
	if attachmentUUID == "" {
		attachmentUUID = uuidBytesToStringOrEmpty(row.FileAttachmentID)
	}
	if actorUUID == "" || helicopterUUID == "" || helicopterFileUUID == "" || attachmentUUID == "" {
		return
	}

	updatedAt := row.UpdatedAt.UTC()
	if updatedAt.IsZero() {
		updatedAt = time.Now().UTC()
	}

	sourceFile := row.SourceFile
	name := ""
	mimeType := ""
	if sourceFile != nil {
		name = strings.TrimSpace(sourceFile.Name)
		mimeType = strings.TrimSpace(sourceFile.MimeType)
	}

	s.events.PublishToUsers([]string{actorUUID}, helicopterfilerealtime.Event{
		EventType:         helicopterfilerealtime.DefaultEventType,
		HelicopterID:      helicopterUUID,
		HelicopterFileID:  helicopterFileUUID,
		FileID:            attachmentUUID,
		Section:           strings.TrimSpace(row.Section),
		Name:              name,
		MimeType:          mimeType,
		AttachmentPending: len(row.FileAttachmentID) != 16 && len(row.SourceFileID) == 16,
		Position:          row.Position,
		IsMandatory:       row.IsMandatory,
		UpdatedAt:         updatedAt.Format(time.RFC3339),
	})
}

func (s *HelicopterFileService) markAttachmentFileActive(ctx context.Context, attachmentID []byte, actorID []byte) error {
	if s == nil || s.fileManager == nil || len(attachmentID) != 16 {
		return nil
	}
	lifecycle, ok := s.fileManager.(filemanager.LifecycleManager)
	if !ok {
		return nil
	}
	attachment, err := s.fileManager.GetAttachmentByID(ctx, attachmentID)
	if err != nil || attachment == nil || len(attachment.FileID) != 16 {
		return err
	}
	return lifecycle.MarkFileActive(ctx, attachment.FileID, actorID)
}

func (s *HelicopterFileService) nextPositionByHelicopterSection(ctx context.Context, helicopterID []byte, section string) (int, error) {
	rows, err := s.repo.ListByHelicopterAndSection(ctx, helicopterID, section)
	if err != nil {
		return 0, err
	}
	maxPos := 0
	for i := range rows {
		if rows[i].Position > maxPos {
			maxPos = rows[i].Position
		}
	}
	return maxPos + 1, nil
}

func uploadIntentBulkError(index int, status int, title, detail string) helicopterfile.UploadIntentBulkItemResult {
	return helicopterfile.UploadIntentBulkItemResult{
		Index:   index,
		Success: false,
		Error: &helicopterfile.BulkItemError{
			Status: status,
			Title:  title,
			Detail: detail,
		},
	}
}

func createBulkError(index int, status int, title, detail string) helicopterfile.CreateBulkItemResult {
	return helicopterfile.CreateBulkItemResult{
		Index:   index,
		Success: false,
		Error: &helicopterfile.BulkItemError{
			Status: status,
			Title:  title,
			Detail: detail,
		},
	}
}

func isValidHelicopterFileSection(section string) bool {
	switch strings.TrimSpace(section) {
	case helicopterfile.SectionBeforeFirstFlightInspection,
		helicopterfile.SectionFlightPreparationAndWB,
		helicopterfile.SectionAfterLastFlightInspection:
		return true
	default:
		return false
	}
}

func uuidBytesToStringOrEmpty(id []byte) string {
	s, err := uuidv7.BytesToString(id)
	if err != nil {
		return ""
	}
	return s
}

func normalizeHelicopterFileSort(sort string) string {
	if normalized := normalizeSortByRules(strings.TrimSpace(sort),
		sortExpr("section ASC, position ASC, created_at ASC", "section DESC, position DESC, created_at DESC", "section"),
		sortField("position"),
		sortField("is_mandatory"),
		sortField("created_at"),
		sortField("updated_at"),
	); normalized != "" {
		return normalized
	}
	return "section ASC, position ASC, created_at ASC"
}
