package processor

import (
	"context"
	"fmt"
	"mime/multipart"
	"path/filepath"
	"strings"
	"time"

	"apskel-pos-be/internal/constants"
	"apskel-pos-be/internal/entities"
	"apskel-pos-be/internal/mappers"
	"apskel-pos-be/internal/models"

	"github.com/google/uuid"
)

type FileProcessor interface {
	UploadFile(ctx context.Context, file *multipart.FileHeader, req *models.UploadFileRequest, organizationID, userID uuid.UUID) (*models.FileResponse, error)
	GetFileByID(ctx context.Context, id uuid.UUID) (*models.FileResponse, error)
	UpdateFile(ctx context.Context, id uuid.UUID, req *models.UpdateFileRequest) (*models.FileResponse, error)
	ListFiles(ctx context.Context, req *models.ListFilesRequest) (*models.ListFilesResponse, error)
	GetFileByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]*models.FileResponse, error)
	GetFileByUserID(ctx context.Context, userID uuid.UUID) ([]*models.FileResponse, error)
}

type FileRepository interface {
	Create(ctx context.Context, file *entities.File) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.File, error)
	GetByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]*entities.File, error)
	GetByUserID(ctx context.Context, userID uuid.UUID) ([]*entities.File, error)
	Update(ctx context.Context, file *entities.File) error
	Delete(ctx context.Context, id uuid.UUID) error
	List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.File, int64, error)
	GetByFileName(ctx context.Context, fileName string) (*entities.File, error)
	ExistsByFileName(ctx context.Context, fileName string) (bool, error)
}

type FileProcessorImpl struct {
	fileRepo   FileRepository
	fileClient FileClient
}

func NewFileProcessorImpl(fileRepo FileRepository, fileClient FileClient) *FileProcessorImpl {
	return &FileProcessorImpl{
		fileRepo:   fileRepo,
		fileClient: fileClient,
	}
}

func (p *FileProcessorImpl) UploadFile(ctx context.Context, file *multipart.FileHeader, req *models.UploadFileRequest, organizationID, userID uuid.UUID) (*models.FileResponse, error) {
	if file == nil {
		return nil, fmt.Errorf("file is required")
	}

	if file.Size == 0 {
		return nil, fmt.Errorf("file cannot be empty")
	}

	const maxFileSize = 10 * 1024 * 1024 // 10MB
	if file.Size > maxFileSize {
		return nil, fmt.Errorf("file size exceeds maximum limit of 10MB")
	}

	if !constants.IsValidFileType(req.FileType) {
		return nil, fmt.Errorf("invalid file type: %s", req.FileType)
	}

	originalName := file.Filename
	fileName := mappers.GenerateFileName(originalName, organizationID, userID)

	for {
		exists, err := p.fileRepo.ExistsByFileName(ctx, fileName)
		if err != nil {
			return nil, fmt.Errorf("failed to check filename uniqueness: %w", err)
		}
		if !exists {
			break
		}

		ext := filepath.Ext(fileName)
		base := strings.TrimSuffix(fileName, ext)
		fileName = fmt.Sprintf("%s_%d%s", base, time.Now().UnixNano(), ext)
	}

	src, err := file.Open()
	if err != nil {
		return nil, fmt.Errorf("failed to open file: %w", err)
	}
	defer src.Close()

	fileContent := make([]byte, file.Size)
	_, err = src.Read(fileContent)
	if err != nil {
		return nil, fmt.Errorf("failed to read file content: %w", err)
	}

	fileURL, err := p.fileClient.UploadFile(ctx, fileName, fileContent)
	if err != nil {
		return nil, fmt.Errorf("failed to upload file to storage: %w", err)
	}

	mimeType := file.Header.Get("Content-Type")
	if mimeType == "" {
		mimeType = "application/octet-stream"
	}

	fileType := req.FileType
	if fileType == "" {
		fileType = constants.GetFileTypeFromMimeType(mimeType)
	}

	fileEntity := mappers.UploadFileRequestToEntity(
		&models.UploadFileRequest{
			FileType: fileType,
			IsPublic: req.IsPublic,
			Metadata: req.Metadata,
		},
		organizationID,
		userID,
		fileName,
		originalName,
		fileURL,
		mimeType,
		fileName,
		file.Size,
	)

	if err := p.fileRepo.Create(ctx, fileEntity); err != nil {
		return nil, fmt.Errorf("failed to save file metadata: %w", err)
	}

	response := mappers.FileEntityToResponse(fileEntity)
	return response, nil
}

func (p *FileProcessorImpl) GetFileByID(ctx context.Context, id uuid.UUID) (*models.FileResponse, error) {
	file, err := p.fileRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("failed to get file: %w", err)
	}

	response := mappers.FileEntityToResponse(file)
	return response, nil
}

func (p *FileProcessorImpl) UpdateFile(ctx context.Context, id uuid.UUID, req *models.UpdateFileRequest) (*models.FileResponse, error) {
	file, err := p.fileRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("failed to get file: %w", err)
	}

	updates := mappers.UpdateFileRequestToEntityUpdates(req)
	for key, value := range updates {
		switch key {
		case "is_public":
			file.IsPublic = value.(bool)
		case "metadata":
			file.Metadata = entities.Metadata(value.(map[string]interface{}))
		}
	}

	if err := p.fileRepo.Update(ctx, file); err != nil {
		return nil, fmt.Errorf("failed to update file: %w", err)
	}

	response := mappers.FileEntityToResponse(file)
	return response, nil
}

func (p *FileProcessorImpl) ListFiles(ctx context.Context, req *models.ListFilesRequest) (*models.ListFilesResponse, error) {
	filters := make(map[string]interface{})

	if req.OrganizationID != nil {
		filters["organization_id"] = *req.OrganizationID
	}
	if req.UserID != nil {
		filters["user_id"] = *req.UserID
	}
	if req.FileType != nil {
		filters["file_type"] = string(*req.FileType)
	}
	if req.IsPublic != nil {
		filters["is_public"] = *req.IsPublic
	}

	offset := (req.Page - 1) * req.Limit

	files, total, err := p.fileRepo.List(ctx, filters, req.Limit, offset)
	if err != nil {
		return nil, fmt.Errorf("failed to list files: %w", err)
	}

	fileResponses := mappers.FileEntitiesToResponses(files)
	totalPages := (int(total) + req.Limit - 1) / req.Limit

	response := &models.ListFilesResponse{
		Files:      fileResponses,
		TotalCount: int(total),
		Page:       req.Page,
		Limit:      req.Limit,
		TotalPages: totalPages,
	}

	return response, nil
}

func (p *FileProcessorImpl) GetFileByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]*models.FileResponse, error) {
	files, err := p.fileRepo.GetByOrganizationID(ctx, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get files by organization: %w", err)
	}

	responses := mappers.FileEntitiesToResponses(files)
	return responses, nil
}

func (p *FileProcessorImpl) GetFileByUserID(ctx context.Context, userID uuid.UUID) ([]*models.FileResponse, error) {
	files, err := p.fileRepo.GetByUserID(ctx, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to get files by user: %w", err)
	}

	responses := mappers.FileEntitiesToResponses(files)
	return responses, nil
}
