// internal/media/upload/blossom/upload.go
package blossom

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/nbd-wtf/go-nostr"
	"git.sovbit.dev/Enki/nostr-poster/internal/utils"
	"go.uber.org/zap"
)

// BlobDescriptor represents metadata about a blob stored with Blossom
type BlobDescriptor struct {
	URL       string     `json:"url,omitempty"`
	SHA256    string     `json:"sha256,omitempty"`
	MimeType  string     `json:"mime_type,omitempty"`
	Size      int64      `json:"size,omitempty"`
	Height    int        `json:"height,omitempty"`
	Width     int        `json:"width,omitempty"`
	Alt       string     `json:"alt,omitempty"`
	Caption   string     `json:"caption,omitempty"`
	CreatedAt time.Time  `json:"created_at,omitempty"`
}

// UploadResponse represents the response from a Blossom upload
type UploadResponse struct {
	Status  string         `json:"status"`
	Message string         `json:"message"`
	Blob    BlobDescriptor `json:"blob"`
}

// Uploader implements the media upload functionality for Blossom
type Uploader struct {
	serverURL string
	logger    *zap.Logger
	// Function to get a signed auth header (for Blossom authentication)
	getAuthHeader func(url, method string) (string, error)
}

// NewUploader creates a new Blossom uploader
func NewUploader(
	serverURL string,
	logger *zap.Logger,
	getAuthHeader func(url, method string) (string, error),
) *Uploader {
	if logger == nil {
		// Create a default logger if none is provided
		var err error
		logger, err = zap.NewProduction()
		if err != nil {
			// If we can't create a logger, use a no-op logger
			logger = zap.NewNop()
		}
	}
	
	return &Uploader{
		serverURL:    serverURL,
		logger:       logger,
		getAuthHeader: getAuthHeader,
	}
}

// UploadFile uploads a file to a Blossom server
func (u *Uploader) UploadFile(filePath string, caption string, altText string) (string, string, error) {
	// Open the file
	file, err := os.Open(filePath)
	if err != nil {
		return "", "", fmt.Errorf("failed to open file: %w", err)
	}
	defer file.Close()
	
	// Get file info
	fileInfo, err := file.Stat()
	if err != nil {
		return "", "", fmt.Errorf("failed to get file info: %w", err)
	}
	
	// Calculate file hash
	hasher := sha256.New()
	if _, err := io.Copy(hasher, file); err != nil {
		return "", "", fmt.Errorf("failed to calculate file hash: %w", err)
	}
	
	// Reset file pointer
	if _, err := file.Seek(0, 0); err != nil {
		return "", "", fmt.Errorf("failed to reset file: %w", err)
	}
	
	// Get hash as hex
	fileHash := hex.EncodeToString(hasher.Sum(nil))
	
	// Get content type
	contentType, err := utils.GetFileContentType(filePath)
	if err != nil {
		return "", "", fmt.Errorf("failed to determine content type: %w", err)
	}
	
	// Create a buffer for the multipart form
	var requestBody bytes.Buffer
	writer := multipart.NewWriter(&requestBody)
	
	// Add the file
	part, err := writer.CreateFormFile("file", filepath.Base(filePath))
	if err != nil {
		return "", "", fmt.Errorf("failed to create form file: %w", err)
	}
	
	if _, err := io.Copy(part, file); err != nil {
		return "", "", fmt.Errorf("failed to copy file to form: %w", err)
	}
	
	// Add caption if provided
	if caption != "" {
		if err := writer.WriteField("caption", caption); err != nil {
			return "", "", fmt.Errorf("failed to add caption: %w", err)
		}
	}
	
	// Add alt text if provided
	if altText != "" {
		if err := writer.WriteField("alt", altText); err != nil {
			return "", "", fmt.Errorf("failed to add alt text: %w", err)
		}
	}
	
	// Add file size information
	if err := writer.WriteField("size", fmt.Sprintf("%d", fileInfo.Size())); err != nil {
		return "", "", fmt.Errorf("failed to add file size: %w", err)
	}
	
	// Add content type
	if err := writer.WriteField("content_type", contentType); err != nil {
		return "", "", fmt.Errorf("failed to add content type: %w", err)
	}
	
	// Add file hash (for integrity verification)
	if err := writer.WriteField("hash", fileHash); err != nil {
		return "", "", fmt.Errorf("failed to add file hash: %w", err)
	}
	
	// Close the writer
	if err := writer.Close(); err != nil {
		return "", "", fmt.Errorf("failed to close multipart writer: %w", err)
	}
	
	// Create the upload URL
	uploadURL := fmt.Sprintf("%s/upload", u.serverURL)
	
	// Create the request
	req, err := http.NewRequest("PUT", uploadURL, &requestBody)
	if err != nil {
		return "", "", fmt.Errorf("failed to create request: %w", err)
	}
	
	// Set content type
	req.Header.Set("Content-Type", writer.FormDataContentType())
	
	// Add authorization header if available
	if u.getAuthHeader != nil {
		authHeader, err := u.getAuthHeader(uploadURL, "PUT")
		if err != nil {
			return "", "", fmt.Errorf("failed to create auth header: %w", err)
		}
		
		req.Header.Set("Authorization", authHeader)
	}
	
	// Create HTTP client with timeout
	client := &http.Client{
		Timeout: 2 * time.Minute,
	}
	
	// Send the request
	resp, err := client.Do(req)
	if err != nil {
		return "", "", fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()
	
	// Check response status
	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return "", "", fmt.Errorf("server returned non-success status: %d, body: %s", resp.StatusCode, string(bodyBytes))
	}
	
	// Parse response
	var uploadResp UploadResponse
	if err := json.NewDecoder(resp.Body).Decode(&uploadResp); err != nil {
		return "", "", fmt.Errorf("failed to parse response: %w", err)
	}
	
	// Check for success
	if uploadResp.Status != "success" {
		return "", "", fmt.Errorf("upload failed: %s", uploadResp.Message)
	}
	
	// Get the media URL
	mediaURL := fmt.Sprintf("%s/%s", u.serverURL, uploadResp.Blob.SHA256)
	
	// Verify the returned hash matches our calculated hash
	if uploadResp.Blob.SHA256 != fileHash {
		u.logger.Warn("Server returned different hash than calculated",
			zap.String("calculated", fileHash),
			zap.String("returned", uploadResp.Blob.SHA256))
	}
	
	return mediaURL, uploadResp.Blob.SHA256, nil
}

// DeleteFile deletes a file from the Blossom server
func (u *Uploader) DeleteFile(fileHash string) error {
	// Create the delete URL
	deleteURL := fmt.Sprintf("%s/%s", u.serverURL, fileHash)
	
	// Create the request
	req, err := http.NewRequest("DELETE", deleteURL, nil)
	if err != nil {
		return fmt.Errorf("failed to create delete request: %w", err)
	}
	
	// Add authorization header if available
	if u.getAuthHeader != nil {
		authHeader, err := u.getAuthHeader(deleteURL, "DELETE")
		if err != nil {
			return fmt.Errorf("failed to create auth header: %w", err)
		}
		
		req.Header.Set("Authorization", authHeader)
	}
	
	// Send the request
	client := &http.Client{
		Timeout: 30 * time.Second,
	}
	
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send delete request: %w", err)
	}
	defer resp.Body.Close()
	
	// Check response status
	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("server returned non-OK status for delete: %d, body: %s", resp.StatusCode, string(bodyBytes))
	}
	
	return nil
}

// CreateBlossomAuthHeader creates a Blossom authentication header
// BUD-01 requires a kind 24242 authorization event
func CreateBlossomAuthHeader(url, method string, privkey string) (string, error) {
	// Create the event
	tags := []nostr.Tag{
		{"u", url},
		{"method", method},
	}
	
	// Create the auth event
	authEvent := nostr.Event{
		Kind:      24242, // Blossom Authorization event
		CreatedAt: nostr.Timestamp(time.Now().Unix()),
		Tags:      tags,
		Content:   "", // Empty content for auth events
	}
	
	// Get the public key
	pubkey, err := nostr.GetPublicKey(privkey)
	if err != nil {
		return "", fmt.Errorf("failed to get public key: %w", err)
	}
	
	authEvent.PubKey = pubkey
	
	// Sign the event
	err = authEvent.Sign(privkey)
	if err != nil {
		return "", fmt.Errorf("failed to sign auth event: %w", err)
	}
	
	// Serialize the event
	eventJSON, err := json.Marshal(authEvent)
	if err != nil {
		return "", fmt.Errorf("failed to serialize auth event: %w", err)
	}
	
	// Encode as base64
	encodedEvent := base64.StdEncoding.EncodeToString(eventJSON)
	
	// Return the authorization header
	return "Nostr " + encodedEvent, nil
}