package company

import (
	"context"
	"errors"
	"mime/multipart"

	"tm/pkg/filestore"
)

const (
	companyDocumentCategory   = "company_document"
	maxCompanyDocumentsUpload = 20
)

// UploadDocumentsResponse is returned after uploading company documents.
type UploadDocumentsResponse struct {
	DocumentFileIDs []string                       `json:"document_file_ids"`
	Uploaded        []filestore.UploadedFileResult `json:"uploaded"`
	Failed          []filestore.FailedUploadResult `json:"failed,omitempty"`
}

// UploadDocuments uploads one or more files and appends their IDs to the company.
func (s *companyService) UploadDocuments(ctx context.Context, companyID, uploadedBy string, files []*multipart.FileHeader) (*UploadDocumentsResponse, error) {
	if s.fileStore == nil {
		return nil, errors.New("file storage is not configured")
	}
	if len(files) == 0 {
		return nil, errors.New("no files provided")
	}
	if len(files) > maxCompanyDocumentsUpload {
		return nil, errors.New("too many files in request")
	}

	company, err := s.repository.GetByID(ctx, companyID)
	if err != nil {
		return nil, errors.New("company not found")
	}

	opts := filestore.UploadOptions{
		UploadedBy: uploadedBy,
		Category:   companyDocumentCategory,
		MaxSize:    filestore.MaxUploadSize,
		Extra: map[string]string{
			"company_id": companyID,
		},
	}

	uploaded := make([]filestore.UploadedFileResult, 0, len(files))
	failed := make([]filestore.FailedUploadResult, 0)
	newIDs := make([]string, 0, len(files))

	for _, file := range files {
		result, uploadErr := filestore.UploadFromMultipartFile(s.fileStore, file, opts)
		if uploadErr != nil {
			code := filestore.ErrCodeUploadFailed
			var fsErr *filestore.FileStoreError
			if errors.As(uploadErr, &fsErr) {
				code = fsErr.Code
			}
			failed = append(failed, filestore.FailedUploadResult{
				Filename: file.Filename,
				Error:    uploadErr.Error(),
				Code:     code,
			})
			continue
		}
		uploaded = append(uploaded, result)
		newIDs = append(newIDs, result.FileID)
	}

	if len(uploaded) == 0 {
		s.logger.Warn("Company document upload failed for all files", map[string]interface{}{
			"company_id": companyID,
			"file_count": len(files),
		})
		return &UploadDocumentsResponse{
			DocumentFileIDs: company.DocumentFileIDs,
			Uploaded:        uploaded,
			Failed:          failed,
		}, errors.New("all file uploads failed")
	}

	company.DocumentFileIDs = mergeDocumentFileIDs(company.DocumentFileIDs, newIDs)
	if err := s.repository.Update(ctx, company); err != nil {
		s.logger.Error("Failed to save company documents", map[string]interface{}{
			"error":      err.Error(),
			"company_id": companyID,
		})
		return nil, errors.New("failed to update company documents")
	}

	s.logger.Info("Company documents uploaded", map[string]interface{}{
		"company_id":      companyID,
		"uploaded_count":  len(uploaded),
		"failed_count":    len(failed),
		"documents_total": len(company.DocumentFileIDs),
	})

	return &UploadDocumentsResponse{
		DocumentFileIDs: company.DocumentFileIDs,
		Uploaded:        uploaded,
		Failed:          failed,
	}, nil
}

func mergeDocumentFileIDs(existing, additional []string) []string {
	seen := make(map[string]struct{}, len(existing)+len(additional))
	merged := make([]string, 0, len(existing)+len(additional))

	appendUnique := func(ids []string) {
		for _, id := range ids {
			if id == "" {
				continue
			}
			if _, ok := seen[id]; ok {
				continue
			}
			seen[id] = struct{}{}
			merged = append(merged, id)
		}
	}

	appendUnique(existing)
	appendUnique(additional)
	return merged
}
