package deploy

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"strings"
	"sync"

	"github.com/nbd-wtf/go-nostr"
	"github.com/studiokaiji/nostr-webhost/hostr/cmd/tools"
)

var availableContentSuffixes = []string{
	".png",
	".jpg",
	".jpeg",
	".gif",
	".webp",
	".mp4",
	".quicktime",
	".mpeg",
	".webm",
	".mpeg",
	".mpg",
	".mpeg3",
	".mp3",
}

const uploadEndpoint = "https://nostrcheck.me/api/v1/media"

type MediaResult struct {
	Result      bool     `json:"result,omitempty"`
	Description string   `json:"description"`
	Status      string   `json:"status,omitempty"`
	Id          int      `json:"id,omitempty"`
	Pubkey      string   `json:"pubkey,omitempty"`
	Url         string   `json:"url,omitempty"`
	Hash        string   `json:"hash,omitempty"`
	Magnet      string   `json:"magnet,omitempty"`
	Tags        []string `json:"tags,omitempty"`
}

// [元パス]:[URL]の形で記録する
var uploadedMediaFilePathToURL = map[string]string{}

func uploadMediaFiles(basePath string, filePaths []string, requests []*http.Request) {
	fmt.Println("Uploading media files...")

	client := &http.Client{}

	var uploadedMediaFilePathToURLCount = 0
	var allMediaFilesCount = len(requests)

	var wg sync.WaitGroup

	go func() {
		wg.Add(1)
		tools.DisplayProgressBar(&uploadedMediaFilePathToURLCount, &allMediaFilesCount)
		wg.Done()
	}()

	var mutex sync.Mutex

	// アップロードを並列処理
	for i, req := range requests {
		wg.Add(1)
		filePath := filePaths[i]

		fmt.Println("Added upload request", filePath)

		go func(filePath string, req *http.Request) {
			defer wg.Done()

			response, err := client.Do(req)
			// リクエストを送信
			if err != nil {
				fmt.Println("\n❌ Error sending request:", filePath, err)
				return
			}
			defer response.Body.Close()

			if !strings.HasPrefix(fmt.Sprint(response.StatusCode), "2") {
				fmt.Println("\n❌ Failed to upload:", response.StatusCode, filePath)
				return
			}

			var result *MediaResult
			// ResultのDecode
			err = json.NewDecoder(response.Body).Decode(&result)

			if err != nil {
				fmt.Println("\n❌ Error decoding response:", err)
				return
			}

			// アップロードに失敗した場合
			if !result.Result {
				fmt.Println("\n❌ Failed to upload file:", filePath, err)
				return
			}

			mutex.Lock()                      // ロックして排他制御
			uploadedMediaFilePathToURLCount++ // カウントアップ
			uploadedMediaFilePathToURL[strings.Replace(filePath, basePath, "", 1)] = result.Url
			mutex.Unlock() // ロック解除
		}(filePath, req)
	}

	wg.Wait()
}

func filePathToUploadMediaRequest(basePath, filePath, priKey, pubKey string) (*http.Request, error) {
	// ファイルを開く
	file, err := os.Open(filePath)
	if err != nil {
		return nil, fmt.Errorf("Failed to read %s: %w", filePath, err)
	}
	defer file.Close()

	// リクエストボディのバッファを初期化
	var requestBody bytes.Buffer
	// multipart writerを作成
	writer := multipart.NewWriter(&requestBody)

	// mediafileフィールドを作成
	part, err := writer.CreateFormFile("mediafile", filePath)
	if err != nil {
		return nil, fmt.Errorf("Error creating form file: %w", err)
	}
	// ファイルの内容をpartにコピー
	_, err = io.Copy(part, file)
	if err != nil {
		return nil, fmt.Errorf("Error copying file: %w", err)
	}

	// uploadtypeフィールドを設定
	err = writer.WriteField("uploadtype", "media")
	if err != nil {
		return nil, fmt.Errorf("Error writing field: %w", err)
	}

	// writerを閉じてリクエストボディを完成させる
	err = writer.Close()
	if err != nil {
		return nil, fmt.Errorf("Error closing writer: %w", err)
	}

	// タグを追加
	tags := nostr.Tags{
		nostr.Tag{"u", uploadEndpoint},
		nostr.Tag{"method", "POST"},
		nostr.Tag{"payload", ""},
	}

	// イベントを生成
	ev, err := getEvent(priKey, pubKey, "", 27235, tags)
	if err != nil {
		return nil, fmt.Errorf("Error get event: %d", err)
	}

	// イベントをJSONにマーシャル
	evJson, err := ev.MarshalJSON()
	if err != nil {
		return nil, fmt.Errorf("Error marshaling event: %d", err)
	}

	// HTTPリクエストを作成
	request, err := http.NewRequest("POST", uploadEndpoint, &requestBody)
	if err != nil {
		return nil, fmt.Errorf("Error creating request: %d", err)
	}

	// ヘッダーを設定
	request.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(evJson))
	request.Header.Set("Accept", "application/json")
	request.Header.Set("Content-Type", writer.FormDataContentType())

	return request, nil
}

// basePath以下のMedia Fileのパスを全て羅列する
func listAllValidStaticMediaFilePaths(basePath string) ([]string, error) {
	return tools.FindFilesWithBasePathBySuffixes(basePath, availableContentSuffixes)
}

// basePath以下のMedia Fileのパスを全て羅列しアップロード
func uploadAllValidStaticMediaFiles(priKey, pubKey, basePath string) error {
	filesPaths, err := listAllValidStaticMediaFilePaths(basePath)
	if err != nil {
		return err
	}

	requests := []*http.Request{}

	for _, filePath := range filesPaths {
		request, err := filePathToUploadMediaRequest(basePath, filePath, priKey, pubKey)
		if err != nil {
			return err
		}
		requests = append(requests, request)
	}

	uploadMediaFiles(basePath, filesPaths, requests)

	return nil
}
