// Package helpers provides shared utilities for WebSocket handlers.
package helpers

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/jfraeys/fetch_ml/internal/experiment"
	"github.com/jfraeys/fetch_ml/internal/fileutil"
	"github.com/jfraeys/fetch_ml/internal/queue"
	"github.com/jfraeys/fetch_ml/internal/worker"
	"github.com/jfraeys/fetch_ml/internal/worker/integrity"
)

// ComputeDatasetID computes a dataset ID from dataset specs or dataset names.
func ComputeDatasetID(datasetSpecs []queue.DatasetSpec, datasets []string) string {
	if len(datasetSpecs) > 0 {
		var checksums []string
		for _, ds := range datasetSpecs {
			if ds.Checksum != "" {
				checksums = append(checksums, ds.Checksum)
			} else if ds.Name != "" {
				checksums = append(checksums, ds.Name)
			}
		}
		if len(checksums) > 0 {
			h := sha256.New()
			for _, cs := range checksums {
				h.Write([]byte(cs))
			}
			return hex.EncodeToString(h.Sum(nil))[:16]
		}
	}
	if len(datasets) > 0 {
		h := sha256.New()
		for _, ds := range datasets {
			h.Write([]byte(ds))
		}
		return hex.EncodeToString(h.Sum(nil))[:16]
	}
	return ""
}

// ComputeParamsHash computes a hash of the args string.
func ComputeParamsHash(args string) string {
	if strings.TrimSpace(args) == "" {
		return ""
	}
	h := sha256.New()
	h.Write([]byte(strings.TrimSpace(args)))
	return hex.EncodeToString(h.Sum(nil))[:16]
}

// FileSHA256Hex computes the SHA256 hash of a file.
// This delegates to the integrity package for consistent hashing.
func FileSHA256Hex(path string) (string, error) {
	return integrity.FileSHA256Hex(path)
}

// ExpectedProvenanceForCommit computes expected provenance metadata for a commit.
func ExpectedProvenanceForCommit(
	expMgr *experiment.Manager,
	commitID string,
) (map[string]string, error) {
	out := map[string]string{}
	manifest, err := expMgr.ReadManifest(commitID)
	if err != nil {
		return nil, err
	}
	if manifest == nil || manifest.OverallSHA == "" {
		return nil, fmt.Errorf("missing manifest overall_sha")
	}
	out["experiment_manifest_overall_sha"] = manifest.OverallSHA

	filesPath := expMgr.GetFilesPath(commitID)
	depName, err := worker.SelectDependencyManifest(filesPath)
	if err == nil && strings.TrimSpace(depName) != "" {
		depPath := filepath.Join(filesPath, depName)
		sha, err := FileSHA256Hex(depPath)
		if err == nil && strings.TrimSpace(sha) != "" {
			out["deps_manifest_name"] = depName
			out["deps_manifest_sha256"] = sha
		}
	}
	return out, nil
}

// EnsureMinimalExperimentFiles ensures minimal experiment files exist.
func EnsureMinimalExperimentFiles(expMgr *experiment.Manager, commitID string) error {
	if expMgr == nil {
		return fmt.Errorf("missing experiment manager")
	}
	commitID = strings.TrimSpace(commitID)
	if commitID == "" {
		return fmt.Errorf("missing commit id")
	}
	filesPath := expMgr.GetFilesPath(commitID)
	if err := os.MkdirAll(filesPath, 0o750); err != nil {
		return err
	}

	trainPath := filepath.Join(filesPath, "train.py")
	if _, err := os.Stat(trainPath); os.IsNotExist(err) {
		if err := fileutil.SecureFileWrite(trainPath, []byte("print('ok')\n"), 0o640); err != nil {
			return err
		}
	}

	reqPath := filepath.Join(filesPath, "requirements.txt")
	if _, err := os.Stat(reqPath); os.IsNotExist(err) {
		if err := fileutil.SecureFileWrite(reqPath, []byte("numpy==1.0.0\n"), 0o640); err != nil {
			return err
		}
	}

	return nil
}
