package service

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"

	"mkiso-server/internal/config"
	"mkiso-server/internal/repo"
	"mkiso-server/pkg/dicom"
)

// RelayService handles DICOM relay (print) to the CD Publisher.
type RelayService struct {
	cfg         *config.Config
	dicomSvc    *DicomService
	patientRepo *repo.PatientRepo
}

// NewRelayService creates a new RelayService.
func NewRelayService(cfg *config.Config, dicomSvc *DicomService, patientRepo *repo.PatientRepo) *RelayService {
	return &RelayService{
		cfg:         cfg,
		dicomSvc:    dicomSvc,
		patientRepo: patientRepo,
	}
}

// Destination returns the CD Publisher address as "host:port".
func (s *RelayService) Destination() string {
	return fmt.Sprintf("%s:%d", s.cfg.CDPublisher.Host, s.cfg.CDPublisher.Port)
}

// RelayResult describes the outcome of a DICOM relay operation.
type RelayResult struct {
	AccessionsSent []string `json:"accessions_sent"`
	PatientName    string   `json:"patient_name"`
	Destination    string   `json:"destination"`
	FilesSent      int      `json:"files_sent"`
}

// RelayToCDPublisher fetches DICOM studies from PACS and forwards them
// to the CD Publisher via storescu (C-STORE).
func (s *RelayService) RelayToCDPublisher(ctx context.Context, accessionNumbers []string) (*RelayResult, error) {
	// Step 1: Try to get patient data from API (graceful if unavailable)
	var patientName string
	patient, err := s.patientRepo.ByAccessionNumber(ctx, accessionNumbers[0])
	if err != nil {
		slog.Warn("patient API lookup failed for relay",
			"accession", accessionNumbers[0],
			"error", err,
		)
	} else if patient != nil {
		patientName = patient.PatientName
		slog.Info("patient data retrieved for relay",
			"accession", accessionNumbers[0],
			"patient", patientName,
		)
	}

	// Step 2: Create temp dir
	tempDir, err := os.MkdirTemp(s.cfg.ISO.TempDir, "dicomdir_")
	if err != nil {
		return nil, fmt.Errorf("create temp dir: %w", err)
	}
	defer os.RemoveAll(tempDir) // guaranteed cleanup

	dicomDir := filepath.Join(tempDir, "DICOMDIR")
	if err := os.MkdirAll(dicomDir, 0755); err != nil {
		return nil, fmt.Errorf("create DICOMDIR: %w", err)
	}

	// Copy microdicom viewer files (optional — CD Publisher doesn't need it,
	// but matches PHP behavior of copying before fetch)
	if dicom.DirExists(s.cfg.ISO.MicrodicomPath) {
		if err := dicom.CopyDir(s.cfg.ISO.MicrodicomPath, tempDir); err != nil {
			slog.Warn("copy microdicom for relay failed", "error", err)
		}
	}

	// Step 3: Fetch DICOM from PACS
	filesRetrieved, err := s.dicomSvc.FetchDICOMMultiple(ctx, accessionNumbers, dicomDir)
	if err != nil {
		return nil, fmt.Errorf("DICOM fetch failed: %w", err)
	}
	slog.Info("DICOM fetched for relay",
		"accessions", accessionNumbers,
		"files", filesRetrieved,
	)

	// Step 4: Relay to CD Publisher via storescu
	destination := fmt.Sprintf("%s:%d", s.cfg.CDPublisher.Host, s.cfg.CDPublisher.Port)
	exitCode, stdout, stderr, err := dicom.RunStoreSCU(
		ctx,
		s.cfg.DCMTK.Storescu,
		s.cfg.OurAE.AETitle,
		s.cfg.CDPublisher.Host,
		s.cfg.CDPublisher.Port,
		dicomDir,
	)
	if err != nil {
		return nil, fmt.Errorf("storescu relay failed (exit %d): %w (stderr: %s)", exitCode, err, stderr)
	}
	_ = stdout

	slog.Info("DICOM relayed to CD Publisher",
		"accessions", accessionNumbers,
		"destination", destination,
		"files", filesRetrieved,
	)

	return &RelayResult{
		AccessionsSent: accessionNumbers,
		PatientName:    patientName,
		Destination:    destination,
		FilesSent:      filesRetrieved,
	}, nil
}
