// Package client provides a Go SDK for interacting with the MapleFile API.
package client

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"
)

// CreatePendingFile creates a new file in pending state.
func (c *Client) CreatePendingFile(ctx context.Context, input *CreateFileInput) (*PendingFile, error) {
	var resp PendingFile
	if err := c.doRequest(ctx, "POST", "/api/v1/files/pending", input, &resp, true); err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetFile returns a single file by ID.
func (c *Client) GetFile(ctx context.Context, id string) (*File, error) {
	path := fmt.Sprintf("/api/v1/file/%s", id)
	var resp File
	if err := c.doRequest(ctx, "GET", path, nil, &resp, true); err != nil {
		return nil, err
	}
	return &resp, nil
}

// UpdateFile updates a file's metadata.
func (c *Client) UpdateFile(ctx context.Context, id string, input *UpdateFileInput) (*File, error) {
	path := fmt.Sprintf("/api/v1/file/%s", id)
	var resp File
	if err := c.doRequest(ctx, "PUT", path, input, &resp, true); err != nil {
		return nil, err
	}
	return &resp, nil
}

// DeleteFile soft-deletes a file.
func (c *Client) DeleteFile(ctx context.Context, id string) error {
	path := fmt.Sprintf("/api/v1/file/%s", id)
	return c.doRequest(ctx, "DELETE", path, nil, nil, true)
}

// DeleteMultipleFiles deletes multiple files at once.
func (c *Client) DeleteMultipleFiles(ctx context.Context, fileIDs []string) error {
	input := DeleteMultipleFilesInput{FileIDs: fileIDs}
	return c.doRequest(ctx, "POST", "/api/v1/files/delete-multiple", input, nil, true)
}

// GetPresignedUploadURL gets a presigned URL for uploading file content.
func (c *Client) GetPresignedUploadURL(ctx context.Context, fileID string) (*PresignedURL, error) {
	path := fmt.Sprintf("/api/v1/file/%s/upload-url", fileID)
	var resp PresignedURL
	if err := c.doRequest(ctx, "GET", path, nil, &resp, true); err != nil {
		return nil, err
	}
	return &resp, nil
}

// CompleteFileUpload marks the file upload as complete and transitions it to active state.
func (c *Client) CompleteFileUpload(ctx context.Context, fileID string, input *CompleteUploadInput) (*File, error) {
	path := fmt.Sprintf("/api/v1/file/%s/complete", fileID)
	var resp File
	if err := c.doRequest(ctx, "POST", path, input, &resp, true); err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetPresignedDownloadURL gets a presigned URL for downloading file content.
func (c *Client) GetPresignedDownloadURL(ctx context.Context, fileID string) (*PresignedDownloadResponse, error) {
	path := fmt.Sprintf("/api/v1/file/%s/download-url", fileID)
	var resp PresignedDownloadResponse
	if err := c.doRequest(ctx, "GET", path, nil, &resp, true); err != nil {
		return nil, err
	}
	return &resp, nil
}

// ReportDownloadCompleted reports that a file download has completed.
func (c *Client) ReportDownloadCompleted(ctx context.Context, fileID string) error {
	path := fmt.Sprintf("/api/v1/file/%s/download-completed", fileID)
	return c.doRequest(ctx, "POST", path, nil, nil, true)
}

// ArchiveFile archives a file.
func (c *Client) ArchiveFile(ctx context.Context, id string) (*File, error) {
	path := fmt.Sprintf("/api/v1/file/%s/archive", id)
	var resp File
	if err := c.doRequest(ctx, "PUT", path, nil, &resp, true); err != nil {
		return nil, err
	}
	return &resp, nil
}

// RestoreFile restores an archived file.
func (c *Client) RestoreFile(ctx context.Context, id string) (*File, error) {
	path := fmt.Sprintf("/api/v1/file/%s/restore", id)
	var resp File
	if err := c.doRequest(ctx, "PUT", path, nil, &resp, true); err != nil {
		return nil, err
	}
	return &resp, nil
}

// ListFilesByCollection returns all files in a collection.
func (c *Client) ListFilesByCollection(ctx context.Context, collectionID string) ([]*File, error) {
	path := fmt.Sprintf("/api/v1/collection/%s/files", collectionID)
	var resp struct {
		Files []*File `json:"files"`
	}
	if err := c.doRequest(ctx, "GET", path, nil, &resp, true); err != nil {
		return nil, err
	}
	return resp.Files, nil
}

// ListRecentFiles returns the user's recent files.
func (c *Client) ListRecentFiles(ctx context.Context) ([]*File, error) {
	var resp struct {
		Files []*File `json:"files"`
	}
	if err := c.doRequest(ctx, "GET", "/api/v1/files/recent", nil, &resp, true); err != nil {
		return nil, err
	}
	return resp.Files, nil
}

// SyncFiles fetches file changes since the given cursor.
func (c *Client) SyncFiles(ctx context.Context, input *SyncInput) (*FileSyncResponse, error) {
	var resp FileSyncResponse
	if err := c.doRequest(ctx, "POST", "/api/v1/files/sync", input, &resp, true); err != nil {
		return nil, err
	}
	return &resp, nil
}

// UploadToPresignedURL uploads data to an S3 presigned URL.
// This is a helper method for uploading encrypted file content directly to S3.
func (c *Client) UploadToPresignedURL(ctx context.Context, presignedURL string, data []byte, contentType string) error {
	req, err := http.NewRequestWithContext(ctx, "PUT", presignedURL, bytes.NewReader(data))
	if err != nil {
		return fmt.Errorf("failed to create upload request: %w", err)
	}

	req.Header.Set("Content-Type", contentType)
	req.ContentLength = int64(len(data))

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to upload to presigned URL: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("upload failed with status %d: %s", resp.StatusCode, string(body))
	}

	return nil
}

// DownloadFromPresignedURL downloads data from an S3 presigned URL.
// This is a helper method for downloading encrypted file content directly from S3.
func (c *Client) DownloadFromPresignedURL(ctx context.Context, presignedURL string) ([]byte, error) {
	req, err := http.NewRequestWithContext(ctx, "GET", presignedURL, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create download request: %w", err)
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to download from presigned URL: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("download failed with status %d: %s", resp.StatusCode, string(body))
	}

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read download response: %w", err)
	}

	return data, nil
}
