package gobackend

import (
	"bufio"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"
)

type AmazonDownloader struct {
	client *http.Client
}

var (
	globalAmazonDownloader *AmazonDownloader
	amazonDownloaderOnce   sync.Once
)

// AfkarXYZResponse is the response from AfkarXYZ API
type AfkarXYZResponse struct {
	Success bool `json:"success"`
	Data    struct {
		DirectLink string `json:"direct_link"`
		FileName   string `json:"file_name"`
		FileSize   int64  `json:"file_size"`
	} `json:"data"`
}

func amazonIsASCIIString(s string) bool {
	for _, r := range s {
		if r > 127 {
			return false
		}
	}
	return true
}

func NewAmazonDownloader() *AmazonDownloader {
	amazonDownloaderOnce.Do(func() {
		globalAmazonDownloader = &AmazonDownloader{
			client: NewHTTPClientWithTimeout(120 * time.Second),
		}
	})
	return globalAmazonDownloader
}

func (a *AmazonDownloader) downloadFromAfkarXYZ(amazonURL string) (string, string, error) {
	apiURL := "https://amazon.afkarxyz.fun/convert?url=" + url.QueryEscape(amazonURL)

	GoLog("[Amazon] Fetching from AfkarXYZ API...\n")

	req, err := http.NewRequest("GET", apiURL, nil)
	if err != nil {
		return "", "", fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("User-Agent", getRandomUserAgent())

	resp, err := a.client.Do(req)
	if err != nil {
		return "", "", fmt.Errorf("failed to call AfkarXYZ API: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return "", "", fmt.Errorf("AfkarXYZ API returned status %d", resp.StatusCode)
	}

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

	var apiResp AfkarXYZResponse
	if err := json.Unmarshal(body, &apiResp); err != nil {
		return "", "", fmt.Errorf("failed to decode response: %w", err)
	}

	if !apiResp.Success || apiResp.Data.DirectLink == "" {
		return "", "", fmt.Errorf("AfkarXYZ API failed or no download link found")
	}

	fileName := apiResp.Data.FileName
	if fileName == "" {
		fileName = "track.flac"
	}

	reg := regexp.MustCompile(`[<>:"/\\|?*]`)
	fileName = reg.ReplaceAllString(fileName, "")

	GoLog("[Amazon] AfkarXYZ returned: %s (%.2f MB)\n", fileName, float64(apiResp.Data.FileSize)/(1024*1024))

	return apiResp.Data.DirectLink, fileName, nil
}

func (a *AmazonDownloader) DownloadFile(downloadURL, outputPath, itemID string) error {
	ctx := context.Background()

	if itemID != "" {
		StartItemProgress(itemID)
		defer CompleteItemProgress(itemID)
		ctx = initDownloadCancel(itemID)
		defer clearDownloadCancel(itemID)
	}

	if isDownloadCancelled(itemID) {
		return ErrDownloadCancelled
	}

	req, err := http.NewRequestWithContext(ctx, "GET", downloadURL, nil)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("User-Agent", getRandomUserAgent())

	resp, err := a.client.Do(req)
	if err != nil {
		if isDownloadCancelled(itemID) {
			return ErrDownloadCancelled
		}
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return fmt.Errorf("download failed: HTTP %d", resp.StatusCode)
	}

	expectedSize := resp.ContentLength
	if expectedSize > 0 && itemID != "" {
		SetItemBytesTotal(itemID, expectedSize)
	}

	out, err := os.Create(outputPath)
	if err != nil {
		return err
	}

	bufWriter := bufio.NewWriterSize(out, 256*1024)

	var written int64
	if itemID != "" {
		pw := NewItemProgressWriter(bufWriter, itemID)
		written, err = io.Copy(pw, resp.Body)
	} else {
		written, err = io.Copy(bufWriter, resp.Body)
	}

	flushErr := bufWriter.Flush()
	closeErr := out.Close()

	if err != nil {
		os.Remove(outputPath)
		if isDownloadCancelled(itemID) {
			return ErrDownloadCancelled
		}
		return fmt.Errorf("download interrupted: %w", err)
	}
	if flushErr != nil {
		os.Remove(outputPath)
		return fmt.Errorf("failed to flush buffer: %w", flushErr)
	}
	if closeErr != nil {
		os.Remove(outputPath)
		return fmt.Errorf("failed to close file: %w", closeErr)
	}

	if expectedSize > 0 && written != expectedSize {
		os.Remove(outputPath)
		return fmt.Errorf("incomplete download: expected %d bytes, got %d bytes", expectedSize, written)
	}

	GoLog("[Amazon] Downloaded: %.2f MB (Complete)\n", float64(written)/(1024*1024))
	return nil
}

// AmazonDownloadResult contains download result with quality info
type AmazonDownloadResult struct {
	FilePath    string
	BitDepth    int
	SampleRate  int
	Title       string
	Artist      string
	Album       string
	ReleaseDate string
	TrackNumber int
	DiscNumber  int
	ISRC        string
}

func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) {
	downloader := NewAmazonDownloader()

	if existingFile, exists := checkISRCExistsInternal(req.OutputDir, req.ISRC); exists {
		return AmazonDownloadResult{FilePath: "EXISTS:" + existingFile}, nil
	}

	songlink := NewSongLinkClient()
	var availability *TrackAvailability
	var err error

	if deezerID, found := strings.CutPrefix(req.SpotifyID, "deezer:"); found {
		GoLog("[Amazon] Using Deezer ID for SongLink lookup: %s\n", deezerID)
		availability, err = songlink.CheckAvailabilityFromDeezer(deezerID)
	} else if req.SpotifyID != "" {
		availability, err = songlink.CheckTrackAvailability(req.SpotifyID, req.ISRC)
	} else {
		return AmazonDownloadResult{}, fmt.Errorf("no valid Spotify or Deezer ID provided for Amazon lookup")
	}

	if err != nil {
		return AmazonDownloadResult{}, fmt.Errorf("failed to check Amazon availability via SongLink: %w", err)
	}

	if !availability.Amazon || availability.AmazonURL == "" {
		return AmazonDownloadResult{}, fmt.Errorf("track not available on Amazon Music (SongLink returned no Amazon URL)")
	}

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

	// Download using AfkarXYZ API
	downloadURL, _, err := downloader.downloadFromAfkarXYZ(availability.AmazonURL)
	if err != nil {
		return AmazonDownloadResult{}, fmt.Errorf("failed to get download URL from AfkarXYZ: %w", err)
	}

	GoLog("[Amazon] Match found: '%s' by '%s'\n", req.TrackName, req.ArtistName)

	filename := buildFilenameFromTemplate(req.FilenameFormat, map[string]any{
		"title":  req.TrackName,
		"artist": req.ArtistName,
		"album":  req.AlbumName,
		"track":  req.TrackNumber,
		"year":   extractYear(req.ReleaseDate),
		"disc":   req.DiscNumber,
	})
	filename = sanitizeFilename(filename) + ".flac"
	outputPath := filepath.Join(req.OutputDir, filename)

	if fileInfo, statErr := os.Stat(outputPath); statErr == nil && fileInfo.Size() > 0 {
		return AmazonDownloadResult{FilePath: "EXISTS:" + outputPath}, nil
	}

	// START PARALLEL: Fetch cover and lyrics while downloading audio
	var parallelResult *ParallelDownloadResult
	parallelDone := make(chan struct{})
	go func() {
		defer close(parallelDone)
		parallelResult = FetchCoverAndLyricsParallel(
			req.CoverURL,
			req.EmbedMaxQualityCover,
			req.SpotifyID,
			req.TrackName,
			req.ArtistName,
			req.EmbedLyrics,
			int64(req.DurationMS),
		)
	}()

	// Download audio file with item ID for progress tracking
	if err := downloader.DownloadFile(downloadURL, outputPath, req.ItemID); err != nil {
		if errors.Is(err, ErrDownloadCancelled) {
			return AmazonDownloadResult{}, ErrDownloadCancelled
		}
		return AmazonDownloadResult{}, fmt.Errorf("download failed: %w", err)
	}

	// Wait for parallel operations to complete
	<-parallelDone

	if req.ItemID != "" {
		SetItemProgress(req.ItemID, 1.0, 0, 0)
		SetItemFinalizing(req.ItemID)
	}

	existingMeta, metaErr := ReadMetadata(outputPath)
	actualTrackNum := req.TrackNumber
	actualDiscNum := req.DiscNumber
	actualDate := req.ReleaseDate
	actualAlbum := req.AlbumName
	actualTitle := req.TrackName
	actualArtist := req.ArtistName

	if metaErr == nil && existingMeta != nil {
		if existingMeta.TrackNumber > 0 && (req.TrackNumber == 0 || req.TrackNumber == 1) {
			actualTrackNum = existingMeta.TrackNumber
			GoLog("[Amazon] Using track number from file: %d (request had: %d)\n", actualTrackNum, req.TrackNumber)
		}
		if existingMeta.DiscNumber > 0 && (req.DiscNumber == 0 || req.DiscNumber == 1) {
			actualDiscNum = existingMeta.DiscNumber
			GoLog("[Amazon] Using disc number from file: %d (request had: %d)\n", actualDiscNum, req.DiscNumber)
		}
		if existingMeta.Date != "" && req.ReleaseDate == "" {
			actualDate = existingMeta.Date
			GoLog("[Amazon] Using release date from file: %s\n", actualDate)
		}
		if existingMeta.Album != "" && req.AlbumName == "" {
			actualAlbum = existingMeta.Album
			GoLog("[Amazon] Using album from file: %s\n", actualAlbum)
		}
		GoLog("[Amazon] Existing metadata - Title: %s, Artist: %s, Album: %s, Date: %s\n",
			existingMeta.Title, existingMeta.Artist, existingMeta.Album, existingMeta.Date)
	}

	metadata := Metadata{
		Title:       actualTitle,
		Artist:      actualArtist,
		Album:       actualAlbum,
		AlbumArtist: req.AlbumArtist,
		Date:        actualDate,
		TrackNumber: actualTrackNum,
		TotalTracks: req.TotalTracks,
		DiscNumber:  actualDiscNum,
		ISRC:        req.ISRC,
		Genre:       req.Genre,
		Label:       req.Label,
		Copyright:   req.Copyright,
	}

	var coverData []byte
	if parallelResult != nil && parallelResult.CoverData != nil && len(parallelResult.CoverData) > 0 {
		coverData = parallelResult.CoverData
		GoLog("[Amazon] Using parallel-fetched cover (%d bytes)\n", len(coverData))
	} else {
		existingCover, coverErr := ExtractCoverArt(outputPath)
		if coverErr == nil && len(existingCover) > 0 {
			coverData = existingCover
			GoLog("[Amazon] Using existing cover from Amazon file (%d bytes)\n", len(coverData))
		} else {
			GoLog("[Amazon] No cover available (parallel fetch failed and no existing cover)\n")
		}
	}

	if err := EmbedMetadataWithCoverData(outputPath, metadata, coverData); err != nil {
		GoLog("[Amazon] Warning: failed to embed metadata: %v\n", err)
	}

	if req.EmbedLyrics && parallelResult != nil && parallelResult.LyricsLRC != "" {
		lyricsMode := req.LyricsMode
		if lyricsMode == "" {
			lyricsMode = "embed"
		}

		if lyricsMode == "external" || lyricsMode == "both" {
			GoLog("[Amazon] Saving external LRC file...\n")
			if lrcPath, lrcErr := SaveLRCFile(outputPath, parallelResult.LyricsLRC); lrcErr != nil {
				GoLog("[Amazon] Warning: failed to save LRC file: %v\n", lrcErr)
			} else {
				GoLog("[Amazon] LRC file saved: %s\n", lrcPath)
			}
		}

		if lyricsMode == "embed" || lyricsMode == "both" {
			GoLog("[Amazon] Embedding parallel-fetched lyrics (%d lines)...\n", len(parallelResult.LyricsData.Lines))
			if embedErr := EmbedLyrics(outputPath, parallelResult.LyricsLRC); embedErr != nil {
				GoLog("[Amazon] Warning: failed to embed lyrics: %v\n", embedErr)
			} else {
				GoLog("[Amazon] Lyrics embedded successfully\n")
			}
		}
	} else if req.EmbedLyrics {
		GoLog("[Amazon] No lyrics available from parallel fetch\n")
	}

	GoLog("[Amazon] Downloaded successfully from Amazon Music\n")

	quality, err := GetAudioQuality(outputPath)
	if err != nil {
		GoLog("[Amazon] Warning: couldn't read quality from file: %v\n", err)
	} else {
		GoLog("[Amazon] Actual quality: %d-bit/%dHz\n", quality.BitDepth, quality.SampleRate)
	}

	finalMeta, metaReadErr := ReadMetadata(outputPath)
	if metaReadErr == nil && finalMeta != nil {
		GoLog("[Amazon] Final metadata from file - Track: %d, Disc: %d, Date: %s\n",
			finalMeta.TrackNumber, finalMeta.DiscNumber, finalMeta.Date)
		actualTrackNum = finalMeta.TrackNumber
		actualDiscNum = finalMeta.DiscNumber
		if finalMeta.Date != "" {
			req.ReleaseDate = finalMeta.Date
		}
	}

	// Add to ISRC index for fast duplicate checking
	AddToISRCIndex(req.OutputDir, req.ISRC, outputPath)

	bitDepth := 0
	sampleRate := 0
	if err == nil {
		bitDepth = quality.BitDepth
		sampleRate = quality.SampleRate
	}

	return AmazonDownloadResult{
		FilePath:    outputPath,
		BitDepth:    bitDepth,
		SampleRate:  sampleRate,
		Title:       req.TrackName,
		Artist:      req.ArtistName,
		Album:       req.AlbumName,
		ReleaseDate: req.ReleaseDate,
		TrackNumber: actualTrackNum,
		DiscNumber:  actualDiscNum,
		ISRC:        req.ISRC,
	}, nil
}
