package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"regexp"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

type SoundCloudTrack struct {
	ID         int    `json:"id"`
	Title      string `json:"title"`
	Permalink  string `json:"permalink"`
	ArtworkURL string `json:"artwork_url"`
	Duration   int    `json:"duration"`
	User       struct {
		Username  string `json:"username"`
		Permalink string `json:"permalink"`
	} `json:"user"`
	Streams struct {
		HTTPMP3128URL string `json:"http_mp3_128_url"`
	} `json:"streams"`
}

func SearchSoundCloud(query string, page int) ([]MusicResult, error) {
	clientID, err := extractClientID()
	if err != nil {
		return searchSoundCloudViaScraping(query, page)
	}

	apiResults, err := searchSoundCloudViaAPI(query, clientID, page)
	if err == nil && len(apiResults) > 0 {
		return convertSoundCloudResults(apiResults), nil
	}

	return searchSoundCloudViaScraping(query, page)
}

func searchSoundCloudViaAPI(query, clientID string, page int) ([]SoundCloudTrack, error) {
	const limit = 10
	offset := (page - 1) * limit

	apiUrl := fmt.Sprintf(
		"https://api-v2.soundcloud.com/search/tracks?q=%s&client_id=%s&limit=%d&offset=%d",
		url.QueryEscape(query),
		clientID,
		limit,
		offset,
	)

	resp, err := http.Get(apiUrl)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("API request failed with status: %d", resp.StatusCode)
	}

	var response struct {
		Collection []SoundCloudTrack `json:"collection"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	return response.Collection, nil
}

func convertSoundCloudResults(tracks []SoundCloudTrack) []MusicResult {
	var results []MusicResult

	for _, track := range tracks {
		thumbnail := strings.Replace(track.ArtworkURL, "large", "t500x500", 1)
		trackURL := fmt.Sprintf("https://soundcloud.com/%s/%s",
			track.User.Permalink,
			track.Permalink,
		)

		results = append(results, MusicResult{
			Title:     track.Title,
			Artist:    track.User.Username,
			URL:       trackURL,
			Thumbnail: thumbnail,
			//AudioURL:  track.Streams.HTTPMP3128URL,
			Source:   "SoundCloud",
			Duration: fmt.Sprintf("%d", track.Duration/1000),
		})
	}
	return results
}

func searchSoundCloudViaScraping(query string, page int) ([]MusicResult, error) {
	searchUrl := fmt.Sprintf("https://soundcloud.com/search/sounds?q=%s", url.QueryEscape(query))
	resp, err := http.Get(searchUrl)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		return nil, err
	}

	var results []MusicResult
	doc.Find("li.searchList__item").Each(func(i int, s *goquery.Selection) {
		titleElem := s.Find("a.soundTitle__title")
		artistElem := s.Find("a.soundTitle__username")
		artworkElem := s.Find(".sound__coverArt")

		title := strings.TrimSpace(titleElem.Text())
		artist := strings.TrimSpace(artistElem.Text())
		href, _ := titleElem.Attr("href")
		thumbnail, _ := artworkElem.Find("span.sc-artwork").Attr("style")

		if thumbnail != "" {
			if matches := regexp.MustCompile(`url\((.*?)\)`).FindStringSubmatch(thumbnail); len(matches) > 1 {
				thumbnail = strings.Trim(matches[1], `"`)
			}
		}

		if title == "" || href == "" {
			return
		}

		trackURL, err := url.Parse(href)
		if err != nil {
			return
		}

		if trackURL.Host == "" {
			trackURL.Scheme = "https"
			trackURL.Host = "soundcloud.com"
		}

		trackURL.Path = strings.ReplaceAll(trackURL.Path, "//", "/")
		fullURL := trackURL.String()

		results = append(results, MusicResult{
			Title:     title,
			Artist:    artist,
			URL:       fullURL,
			Thumbnail: thumbnail,
			Source:    "SoundCloud",
		})
	})

	return results, nil
}

func extractClientID() (string, error) {
	resp, err := http.Get("https://soundcloud.com/")
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		return "", err
	}

	var clientID string
	doc.Find("script[src]").Each(func(i int, s *goquery.Selection) {
		if clientID != "" {
			return
		}

		src, _ := s.Attr("src")
		if strings.Contains(src, "sndcdn.com/assets/") {
			resp, err := http.Get(src)
			if err != nil {
				return
			}
			defer resp.Body.Close()

			body, _ := io.ReadAll(resp.Body)
			re := regexp.MustCompile(`client_id:"([^"]+)"`)
			matches := re.FindSubmatch(body)
			if len(matches) > 1 {
				clientID = string(matches[1])
			}
		}
	})

	if clientID == "" {
		return "", fmt.Errorf("client_id not found")
	}
	return clientID, nil
}