package agate

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"unprism.ru/KRBL/agate/grpc"

	"unprism.ru/KRBL/agate/store"
)

// AgateOptions defines configuration options for the Agate library.
type AgateOptions struct {
	// WorkDir is the directory where snapshots will be stored and managed.
	WorkDir string

	// OpenFunc is called after a snapshot is restored to initialize resources.
	// The parameter is the directory where the snapshot was extracted.
	OpenFunc func(dir string) error

	// CloseFunc is called before a snapshot is created or restored to clean up resources.
	CloseFunc func() error

	// MetadataStore is the implementation of the metadata store to use.
	// Use the stores package to initialize the default implementation:
	//   metadataStore, err := stores.NewDefaultMetadataStore(metadataDir)
	MetadataStore store.MetadataStore

	// BlobStore is the implementation of the blob store to use.
	// Use the stores package to initialize the default implementation:
	//   blobStore, err := stores.NewDefaultBlobStore(blobsDir)
	BlobStore store.BlobStore
}

// Agate is the main entry point for the snapshot library.
type Agate struct {
	manager     SnapshotManager
	options     AgateOptions
	metadataDir string
	blobsDir    string
}

// New initializes a new instance of the Agate library with the given options.
func New(options AgateOptions) (*Agate, error) {
	if options.WorkDir == "" {
		return nil, errors.New("work directory cannot be empty")
	}

	// Create the work directory if it doesn't exist
	if err := os.MkdirAll(options.WorkDir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create work directory: %w", err)
	}

	// Create subdirectories for metadata and blobs
	metadataDir := filepath.Join(options.WorkDir, "metadata")
	blobsDir := filepath.Join(options.WorkDir, "blobs")

	if err := os.MkdirAll(metadataDir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create metadata directory: %w", err)
	}

	if err := os.MkdirAll(blobsDir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create blobs directory: %w", err)
	}

	var metadataStore store.MetadataStore
	var blobStore store.BlobStore
	var err error

	// Use provided stores or initialize default ones
	if options.MetadataStore != nil {
		metadataStore = options.MetadataStore
	} else {
		// For default implementation, the user needs to initialize and provide the stores
		return nil, errors.New("metadata store must be provided")
	}

	if options.BlobStore != nil {
		blobStore = options.BlobStore
	} else {
		// For default implementation, the user needs to initialize and provide the stores
		return nil, errors.New("blob store must be provided")
	}

	// Create the snapshot manager
	manager, err := CreateSnapshotManager(metadataStore, blobStore)
	if err != nil {
		return nil, fmt.Errorf("failed to create snapshot manager: %w", err)
	}

	return &Agate{
		manager:     manager,
		options:     options,
		metadataDir: metadataDir,
		blobsDir:    blobsDir,
	}, nil
}

// SaveSnapshot creates a new snapshot from the current state of the work directory.
// If parentID is provided, it will be set as the parent of the new snapshot.
// Returns the ID of the created snapshot.
func (a *Agate) SaveSnapshot(ctx context.Context, name string, parentID string) (string, error) {
	// Call CloseFunc if provided
	if a.options.CloseFunc != nil {
		if err := a.options.CloseFunc(); err != nil {
			return "", fmt.Errorf("failed to close resources before snapshot: %w", err)
		}
	}

	// Create the snapshot
	snapshot, err := a.manager.CreateSnapshot(ctx, a.options.WorkDir, name, parentID)
	if err != nil {
		return "", fmt.Errorf("failed to create snapshot: %w", err)
	}

	// Call OpenFunc if provided
	if a.options.OpenFunc != nil {
		if err := a.options.OpenFunc(a.options.WorkDir); err != nil {
			return "", fmt.Errorf("failed to open resources after snapshot: %w", err)
		}
	}

	return snapshot.ID, nil
}

// RestoreSnapshot extracts a snapshot to the work directory.
func (a *Agate) RestoreSnapshot(ctx context.Context, snapshotID string) error {
	// Call CloseFunc if provided
	if a.options.CloseFunc != nil {
		if err := a.options.CloseFunc(); err != nil {
			return fmt.Errorf("failed to close resources before restore: %w", err)
		}
	}

	// Extract the snapshot
	if err := a.manager.ExtractSnapshot(ctx, snapshotID, a.options.WorkDir); err != nil {
		return fmt.Errorf("failed to extract snapshot: %w", err)
	}

	// Call OpenFunc if provided
	if a.options.OpenFunc != nil {
		if err := a.options.OpenFunc(a.options.WorkDir); err != nil {
			return fmt.Errorf("failed to open resources after restore: %w", err)
		}
	}

	return nil
}

// ListSnapshots returns a list of all available snapshots.
func (a *Agate) ListSnapshots(ctx context.Context) ([]store.SnapshotInfo, error) {
	return a.manager.ListSnapshots(ctx)
}

// GetSnapshotDetails returns detailed information about a specific snapshot.
func (a *Agate) GetSnapshotDetails(ctx context.Context, snapshotID string) (*store.Snapshot, error) {
	return a.manager.GetSnapshotDetails(ctx, snapshotID)
}

// DeleteSnapshot removes a snapshot.
func (a *Agate) DeleteSnapshot(ctx context.Context, snapshotID string) error {
	return a.manager.DeleteSnapshot(ctx, snapshotID)
}

// Close releases all resources used by the Agate instance.
func (a *Agate) Close() error {
	// Currently, we don't have a way to close the manager directly
	// This would be a good addition in the future
	return nil
}

// StartServer starts a gRPC server to share snapshots.
func (a *Agate) StartServer(ctx context.Context, address string) error {
	_, err := grpc.RunServer(ctx, a.manager, address)
	if err != nil {
		return fmt.Errorf("failed to start server: %w", err)
	}

	// We don't store the server reference because we don't have a way to stop it yet
	// In a future version, we could add a StopServer method

	return nil
}

// ConnectRemote connects to a remote snapshot server.
// Returns a client that can be used to interact with the remote server.
func (a *Agate) ConnectRemote(address string) (*grpc.SnapshotClient, error) {
	client, err := grpc.ConnectToServer(address)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to remote server: %w", err)
	}

	return client, nil
}

// GetRemoteSnapshotList retrieves a list of snapshots from a remote server.
func (a *Agate) GetRemoteSnapshotList(ctx context.Context, address string) ([]store.SnapshotInfo, error) {
	client, err := a.ConnectRemote(address)
	if err != nil {
		return nil, err
	}
	defer client.Close()

	return client.ListSnapshots(ctx)
}

// GetRemoteSnapshot downloads a snapshot from a remote server.
// If localParentID is provided, it will be used to optimize the download by skipping files that already exist locally.
func (a *Agate) GetRemoteSnapshot(ctx context.Context, address string, snapshotID string, localParentID string) error {
	client, err := a.ConnectRemote(address)
	if err != nil {
		return err
	}
	defer client.Close()

	// Create a temporary directory for the downloaded snapshot
	tempDir := filepath.Join(a.options.WorkDir, "temp", snapshotID)
	if err := os.MkdirAll(tempDir, 0755); err != nil {
		return fmt.Errorf("failed to create temporary directory: %w", err)
	}

	// Download the snapshot
	if err := client.DownloadSnapshot(ctx, snapshotID, tempDir, localParentID); err != nil {
		return fmt.Errorf("failed to download snapshot: %w", err)
	}

	// Get the snapshot details to create a local copy
	details, err := client.FetchSnapshotDetails(ctx, snapshotID)
	if err != nil {
		return fmt.Errorf("failed to get snapshot details: %w", err)
	}

	// Create a local snapshot from the downloaded files
	_, err = a.manager.CreateSnapshot(ctx, tempDir, details.Name, localParentID)
	if err != nil {
		return fmt.Errorf("failed to create local snapshot: %w", err)
	}

	// Clean up the temporary directory
	os.RemoveAll(tempDir)

	return nil
}
