package store

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/contracts/graph"
)

var (
	// ErrArtifactNotFound indicates the requested artifact or delta does not exist.
	ErrArtifactNotFound = errors.New("artifact not found")

	// ErrInvalidArtifactID indicates the artifact or delta ID format is invalid.
	ErrInvalidArtifactID = errors.New("invalid artifact id")
)

// FilesystemReader loads graph artifacts and deltas from a local JSON store.
type FilesystemReader struct {
	storePath string
}

// NewFilesystemReader creates a reader at storePath.
func NewFilesystemReader(storePath string) (*FilesystemReader, error) {
	if strings.TrimSpace(storePath) == "" {
		return nil, errors.New("artifact store path is required")
	}

	absPath, err := filepath.Abs(storePath)
	if err != nil {
		return nil, fmt.Errorf("resolve artifact store path: %w", err)
	}

	return &FilesystemReader{storePath: absPath}, nil
}

// LoadDeltaByURI loads a graph delta from a file:// URI or path under the store.
func (r *FilesystemReader) LoadDeltaByURI(ctx context.Context, deltaURI string) (graph.Delta, error) {
	var empty graph.Delta
	path, err := r.resolveURI(deltaURI)
	if err != nil {
		return empty, err
	}
	return r.readDeltaFile(ctx, path)
}

// LoadDeltaByID loads a graph delta by deterministic delta artifact ID.
func (r *FilesystemReader) LoadDeltaByID(ctx context.Context, deltaID string) (graph.Delta, error) {
	if err := validateArtifactID(deltaID); err != nil {
		var empty graph.Delta
		return empty, err
	}
	return r.readDeltaFile(ctx, r.deltaPath(deltaID))
}

// LoadArtifactByURI loads a graph artifact from a file:// URI or path under the store.
func (r *FilesystemReader) LoadArtifactByURI(ctx context.Context, artifactURI string) (graph.Artifact, error) {
	var empty graph.Artifact
	path, err := r.resolveURI(artifactURI)
	if err != nil {
		return empty, err
	}
	return r.readArtifactFile(ctx, path)
}

func (r *FilesystemReader) deltaPath(deltaID string) string {
	return filepath.Join(r.storePath, deltaID+".json")
}

func (r *FilesystemReader) artifactPath(artifactID string) string {
	return filepath.Join(r.storePath, artifactID+".json")
}

func (r *FilesystemReader) resolveURI(uri string) (string, error) {
	trimmed := strings.TrimSpace(uri)
	if trimmed == "" {
		return "", fmt.Errorf("artifact uri is required")
	}

	path := trimmed
	if strings.HasPrefix(trimmed, "file://") {
		path = strings.TrimPrefix(trimmed, "file://")
	}

	absPath, err := filepath.Abs(path)
	if err != nil {
		return "", fmt.Errorf("resolve artifact path: %w", err)
	}

	if err := r.ensureUnderStore(absPath); err != nil {
		return "", err
	}
	return absPath, nil
}

func (r *FilesystemReader) ensureUnderStore(absPath string) error {
	cleanPath := filepath.Clean(absPath)
	cleanStore := filepath.Clean(r.storePath)
	if cleanPath == cleanStore {
		return nil
	}
	rel, err := filepath.Rel(cleanStore, cleanPath)
	if err != nil {
		return fmt.Errorf("path outside artifact store: %w", err)
	}
	if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
		return fmt.Errorf("path outside artifact store: %s", absPath)
	}
	return nil
}

func (r *FilesystemReader) readDeltaFile(ctx context.Context, path string) (graph.Delta, error) {
	var delta graph.Delta
	if err := ctx.Err(); err != nil {
		return delta, err
	}

	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return delta, fmt.Errorf("%w: %s", ErrArtifactNotFound, path)
		}
		return delta, fmt.Errorf("read delta file: %w", err)
	}

	if err := json.Unmarshal(data, &delta); err != nil {
		return delta, fmt.Errorf("unmarshal delta: %w", err)
	}
	if err := validateDelta(delta); err != nil {
		return delta, err
	}
	return delta, nil
}

func (r *FilesystemReader) readArtifactFile(ctx context.Context, path string) (graph.Artifact, error) {
	var artifact graph.Artifact
	if err := ctx.Err(); err != nil {
		return artifact, err
	}

	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return artifact, fmt.Errorf("%w: %s", ErrArtifactNotFound, path)
		}
		return artifact, fmt.Errorf("read artifact file: %w", err)
	}

	if err := json.Unmarshal(data, &artifact); err != nil {
		return artifact, fmt.Errorf("unmarshal artifact: %w", err)
	}
	if err := validateArtifact(artifact); err != nil {
		return artifact, err
	}
	return artifact, nil
}

func validateDelta(delta graph.Delta) error {
	version := strings.TrimSpace(delta.SchemaVersion)
	if version == "" {
		delta.SchemaVersion = graph.DeltaSchemaVersionV1
		version = delta.SchemaVersion
	}
	if version != graph.DeltaSchemaVersionV1 {
		return fmt.Errorf("unsupported delta schema_version: %s", delta.SchemaVersion)
	}
	if strings.TrimSpace(delta.RepoID) == "" {
		return errors.New("delta repo_id is required")
	}
	if strings.TrimSpace(delta.BaseCommitSHA) == "" {
		return errors.New("delta base_commit_sha is required")
	}
	if strings.TrimSpace(delta.TargetCommitSHA) == "" {
		return errors.New("delta target_commit_sha is required")
	}
	return nil
}

func validateArtifact(artifact graph.Artifact) error {
	version := strings.TrimSpace(artifact.SchemaVersion)
	if version == "" {
		artifact.SchemaVersion = graph.SchemaVersionV1
		version = artifact.SchemaVersion
	}
	if version != graph.SchemaVersionV1 {
		return fmt.Errorf("unsupported artifact schema_version: %s", artifact.SchemaVersion)
	}
	if strings.TrimSpace(artifact.RepoID) == "" {
		return errors.New("artifact repo_id is required")
	}
	if strings.TrimSpace(artifact.SnapshotID) == "" {
		return errors.New("artifact snapshot_id is required")
	}
	if strings.TrimSpace(artifact.CommitSHA) == "" {
		return errors.New("artifact commit_sha is required")
	}
	return nil
}

func validateArtifactID(id string) error {
	if strings.TrimSpace(id) == "" {
		return ErrInvalidArtifactID
	}
	if strings.Contains(id, "..") || strings.ContainsAny(id, `/\`) {
		return ErrInvalidArtifactID
	}
	return nil
}
