package spotifyconsumeranalytics

import (
	"bufio"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/aws/aws-sdk-go/aws/awserr"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/s3"
)

type spotifyUser struct {
	UserID      string `json:"user_id"`
	CountryCode string `json:"country"`
	Access      string `json:"access"`
}

type spotifyTrack struct {
	TrackID      string `json:"track_id"`
	TrackURI     string `json:"uri"`
	ISRC         string `json:"isrc"`
	AlbumUPC     string `json:"album_code"`
	AlbumArtist  string `json:"album_artist"`
	AlbumName    string `json:"album_name"`
	TrackArtists string `json:"track_artists"`
	TrackName    string `json:"track_name"`
}

var cacheTracksKey string
var cacheTracks map[string]spotifyTrack
var cacheUsersKey string
var cacheUsers map[string]spotifyUser

func makeSpotifySourceUsersS3Key(date string, licensor string) string {
	l := apolloToDelphiLicensor[licensor]
	d := strings.ReplaceAll(date, "-", "")

	if date >= "2020-06-01" {
		return "spotify/users/v4/report_date=" + date + "/report_licensor=" + l + "/users_" + d + ".gz"
	} else if date >= "2018-01-22" {
		return "spotify/users/v2/report_date=" + date + "/report_licensor=" + l + "/users_" + d + ".gz"
	}
	return "spotify/users/v1/report_date=" + date + "/report_licensor=" + l + "/users_" + d + ".gz"
}

func makeSpotifySourceTracksS3Key(date string, licensor string) string {
	l := apolloToDelphiLicensor[licensor]
	d := strings.ReplaceAll(date, "-", "")
	return "spotify/tracks/v1/report_date=" + date + "/report_licensor=" + l + "/tracks_" + d + ".gz"
}

func makeSpotifySourceStreamsS3Key(date string, licensor string, countryCode string) string {
	l := apolloToDelphiLicensor[licensor]
	d := strings.ReplaceAll(date, "-", "")
	c := strings.ToUpper(countryCode)

	if date >= "2024-08-12" {
		return "spotify/streams/v5/report_date=" + date + "/report_licensor=" + l + "/streams_" + d + "_" + c + ".gz"
	} else if date >= "2020-06-01" {
		return "spotify/streams/v4/report_date=" + date + "/report_licensor=" + l + "/streams_" + d + "_" + c + ".gz"
	} else if date >= "2018-01-22" {
		return "spotify/streams/v2/report_date=" + date + "/report_licensor=" + l + "/streams_" + d + "_" + c + ".gz"
	}
	return "spotify/streams/v1/report_date=" + date + "/report_licensor=" + l + "/streams_" + d + "_" + c + ".gz"
}

func makeSourceAggregatedStreamsS3Key(date string, licensor string) string {
	l := apolloToDelphiLicensor[licensor]
	d := strings.ReplaceAll(date, "-", "")
	return "spotify/aggregatedstreams/v2/report_date=" + date + "/report_licensor=" + l + "/aggregatedstreams_" + d + ".gz"
}

func makeSpotifyTargetStreamsTracksS3Key(date string, licensor string, countryCode string) string {
	return "spotify/" + date + "/spotify_" + date + "_" + licensor + "_v2_" + countryCode + "_streams_tracks.ndjson"
}

func makeSpotifyTargetStreamsPlaylistsS3Key(date string, licensor string, countryCode string) string {
	return "spotify/" + date + "/spotify_" + date + "_" + licensor + "_v2_" + countryCode + "_streams_playlists.ndjson"
}

func makeSpotifyTargetStreamsPlaylistTracksS3Key(date string, licensor string, countryCode string) string {
	return "spotify/" + date + "/spotify_" + date + "_" + licensor + "_v2_" + countryCode + "_streams_playlist_tracks.ndjson"
}

func makeSpotifyTargetStreamsSummaryS3Key(date string, licensor string, countryCode string) string {
	return "spotify/" + date + "/spotify_" + date + "_" + licensor + "_v2_" + countryCode + "_streams_summary.ndjson"
}

func makeTargetSavesSkipsTracksS3Key(date string, licensor string) string {
	return "spotify/" + date + "/spotify_" + date + "_" + licensor + "_v2_" + "saves_skips_tracks.ndjson"
}

func makeTargetDemographicsTracksS3Key(date string, licensor string) string {
	return "spotify/" + date + "/spotify_" + date + "_" + licensor + "_v2_" + "demographics_tracks.ndjson"
}

func makeTargetListenersTracksS3Key(date string, licensor string, countryCode string) string {
	return "spotify/v0/" + date + "/spotify_" + date + "_" + licensor + "_v1_" + countryCode + "_listeners_tracks.ndjson"
}

func makeTargetListenersPlaylistsS3Key(date string, licensor string, countryCode string) string {
	return "spotify/v0/" + date + "/spotify_" + date + "_" + licensor + "_v1_" + countryCode + "_listeners_playlists.ndjson"
}

func makeTargetListenersPlaylistTracksS3Key(date string, licensor string, countryCode string) string {
	return "spotify/v0/" + date + "/spotify_" + date + "_" + licensor + "_v1_" + countryCode + "_listeners_playlist_tracks.ndjson"
}

func makeTargetListenerSummaryTracksS3Key(date string, licensor string) string {
	return "spotify/" + date + "/spotify_" + date + "_" + licensor + "_v1_" + "listenersummary_tracks.ndjson"
}

func makeTargetListenerSummaryPlaylistsS3Key(date string, licensor string) string {
	return "spotify/" + date + "/spotify_" + date + "_" + licensor + "_v1_" + "listenersummary_playlists.ndjson"
}

func makeTargetListenerSummaryPlaylistTracksS3Key(date string, licensor string) string {
	return "spotify/" + date + "/spotify_" + date + "_" + licensor + "_v1_" + "listenersummary_playlist_tracks.ndjson"
}

func targetSpotifyStreamsSummaryFileExists(s3Service *s3.S3, s3TargetBucket, date, licensor, countryCode string) (bool, error) {
	key := makeSpotifyTargetStreamsSummaryS3Key(date, licensor, countryCode)
	input := &s3.HeadObjectInput{
		Bucket: aws.String(s3TargetBucket),
		Key:    aws.String(key),
	}

	if _, err := s3Service.HeadObject(input); err != nil {
		if aerr, ok := err.(awserr.RequestFailure); ok {
			if aerr.StatusCode() == http.StatusNotFound {
				return false, nil
			}
		}
		return false, fmt.Errorf("targetSpotifyStreamsSummaryFileExists, %s: %s", key, err)
	}
	return true, nil
}

func sourceSpotifyTracksFileExists(s3Service *s3.S3, s3SourceBucket, date, licensor string) (bool, error) {
	key := makeSpotifySourceTracksS3Key(date, licensor)
	input := &s3.HeadObjectInput{
		Bucket: aws.String(s3SourceBucket),
		Key:    aws.String(key),
	}

	if _, err := s3Service.HeadObject(input); err != nil {
		if aerr, ok := err.(awserr.RequestFailure); ok {
			if aerr.StatusCode() == http.StatusNotFound {
				return false, nil
			}
		}
		return false, fmt.Errorf("sourceSpotifyTracksFileExists, %s, %s: %s", s3SourceBucket, key, err)
	}
	return true, nil
}

func sourceSpotifyUsersFileExists(s3Service *s3.S3, s3SourceBucket, date, licensor string) (bool, error) {
	key := makeSpotifySourceUsersS3Key(date, licensor)
	input := &s3.HeadObjectInput{
		Bucket: aws.String(s3SourceBucket),
		Key:    aws.String(key),
	}

	if _, err := s3Service.HeadObject(input); err != nil {
		if aerr, ok := err.(awserr.RequestFailure); ok {
			if aerr.StatusCode() == http.StatusNotFound {
				return false, nil
			}
		}
		return false, fmt.Errorf("sourceSpotifyUsersFileExists, %s, %s: %s", s3SourceBucket, key, err)
	}

	return true, nil
}

func sourceSpotifyStreamsFileExists(s3Service *s3.S3, s3SourceBucket, date, licensor, countryCode string) (bool, error) {
	key := makeSpotifySourceStreamsS3Key(date, licensor, countryCode)
	input := &s3.HeadObjectInput{
		Bucket: aws.String(s3SourceBucket),
		Key:    aws.String(key),
	}
	if _, err := s3Service.HeadObject(input); err != nil {
		if aerr, ok := err.(awserr.RequestFailure); ok {
			if aerr.StatusCode() == http.StatusNotFound {
				return false, nil
			}
		}
		return false, fmt.Errorf("sourceSpotifyStreamsFileExists, %s, %s: %s", s3SourceBucket, key, err)
	}
	return true, nil
}

func sourceAggregatedStreamsFileExists(s3Service *s3.S3, s3SourceBucket, date, licensor string) (bool, error) {
	key := makeSourceAggregatedStreamsS3Key(date, licensor)
	return sourceFileExists(s3Service, s3SourceBucket, key)
}

func sourceFileExists(s3Service *s3.S3, s3SourceBucket, key string) (bool, error) {
	return s3FileExists(s3Service, s3SourceBucket, key)
}

func targetFileExists(s3Service *s3.S3, s3TargetBucket, key string) (bool, error) {
	return s3FileExists(s3Service, s3TargetBucket, key)
}

func targetListenersTracksFileExists(s3Service *s3.S3, s3TargetBucket, date, licensor, countryCode string) (bool, error) {
	key := makeTargetListenersTracksS3Key(date, licensor, countryCode)
	return s3FileExists(s3Service, s3TargetBucket, key)
}

func targetListenerSummaryTracksFileExists(s3Service *s3.S3, s3TargetBucket, date, licensor string) (bool, error) {
	key := makeTargetListenerSummaryTracksS3Key(date, licensor)
	return s3FileExists(s3Service, s3TargetBucket, key)
}

func s3FileExists(s3Service *s3.S3, bucket, key string) (bool, error) {
	input := &s3.HeadObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	}

	if _, err := s3Service.HeadObject(input); err != nil {
		if aerr, ok := err.(awserr.RequestFailure); ok {
			if aerr.StatusCode() == http.StatusNotFound {
				return false, nil
			}
		}
		return false, fmt.Errorf("s3FileExists, %s: %s", key, err)
	}
	return true, nil
}

func getSpotifyUsers(s3Service *s3.S3, s3SourceBucket, date string, licensor string) map[string]spotifyUser {
	cacheKey := date + licensor

	if cacheUsersKey != cacheKey {
		cacheUsers = make(map[string]spotifyUser)
	} else {
		return cacheUsers
	}

	input := &s3.GetObjectInput{
		Bucket: aws.String(s3SourceBucket),
		Key:    aws.String(makeSpotifySourceUsersS3Key(date, licensor)),
	}

	result, err := s3Service.GetObject(input)
	if err != nil {
		log.Fatalf("getSpotifyUsers: %s", err)
	}

	users := make(map[string]spotifyUser)

	reader, err := gzip.NewReader(result.Body)
	if err != nil {
		log.Fatal(err)
	}
	defer reader.Close()

	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		var user spotifyUser
		json.Unmarshal([]byte(scanner.Text()), &user)
		user.CountryCode = strings.ToLower(user.CountryCode)
		user.Access = strings.ToLower(user.Access)
		if user.Access != "free" {
			user.Access = "paid"
		}
		users[user.UserID] = user
	}
	if err := scanner.Err(); err != nil {
		log.Fatalf("getSpotifyUsers: %s", err)
	}

	cacheUsersKey = cacheKey
	cacheUsers = users

	return users
}

func getSpotifyTracks(s3Service *s3.S3, s3SourceBucket, date, licensor string) map[string]spotifyTrack {
	cacheKey := date + licensor

	if cacheTracksKey != cacheKey {
		cacheTracks = make(map[string]spotifyTrack)
	} else {
		return cacheTracks
	}

	input := &s3.GetObjectInput{
		Bucket: aws.String(s3SourceBucket),
		Key:    aws.String(makeSpotifySourceTracksS3Key(date, licensor)),
	}

	result, err := s3Service.GetObject(input)
	if err != nil {
		log.Fatal(err)
	}

	tracks := make(map[string]spotifyTrack)

	reader, err := gzip.NewReader(result.Body)
	if err != nil {
		log.Fatal(err)
	}
	defer reader.Close()

	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		var track spotifyTrack
		json.Unmarshal([]byte(scanner.Text()), &track)
		tracks[track.TrackID] = track
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

	cacheTracksKey = cacheKey
	cacheTracks = tracks

	return tracks
}

func getTracksMapURIToISRC(s3Service *s3.S3, s3SourceBucket, date, licensor string) map[string]string {
	key := makeSpotifySourceTracksS3Key(date, licensor)
	input := &s3.GetObjectInput{
		Bucket: aws.String(s3SourceBucket),
		Key:    aws.String(key),
	}

	result, err := s3Service.GetObject(input)
	if err != nil {
		log.Fatalf("getTracksMapURIToISRC, %s: %s", key, err)
	}

	tracks := make(map[string]string)

	reader, err := gzip.NewReader(result.Body)
	if err != nil {
		log.Fatalf("getTracksMapURIToISRC, %s: %s", key, err)
	}
	defer reader.Close()

	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		var track spotifyTrack
		json.Unmarshal([]byte(scanner.Text()), &track)
		tracks[track.TrackURI] = track.ISRC
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

	return tracks
}
