package serra

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"

	"go.mongodb.org/mongo-driver/bson/primitive"
)

type Card struct {
	// Added by Serra
	SerraCount           int64              `bson:"serra_count"`
	SerraCountFoil       int64              `bson:"serra_count_foil"`
	SerraCountEtched     int64              `bson:"serra_count_etched"`
	SerraCountDeck       int64              `bson:"serra_count_deck"`
	SerraCountFoilDeck   int64              `bson:"serra_count_foil_deck"`
	SerraCountEtchedDeck int64              `bson:"serra_count_etched_deck"`
	SerraPrices          []PriceEntry       `bson:"serra_prices"`
	SerraCreated         primitive.DateTime `bson:"serra_created"`
	SerraUpdated         primitive.DateTime `bson:"serra_updated"`
	SerraImage64         string             `bson:"serra_image"`

	Artist          string   `json:"artist"`
	ArtistIds       []string `json:"artist_ids"`
	Booster         bool     `json:"booster"`
	BorderColor     string   `json:"border_color"`
	CardBackID      string   `json:"card_back_id"`
	CardmarketID    float64  `json:"cardmarket_id"`
	Cmc             float64  `json:"cmc"`
	CollectorNumber string   `json:"collector_number"`
	ColorIdentity   []string `json:"color_identity"`
	Colors          []string `json:"colors"`
	Digital         bool     `json:"digital"`
	EdhrecRank      int64    `json:"edhrec_rank"`
	Finishes        []string `json:"finishes"`
	Foil            bool     `json:"foil"`
	Frame           string   `json:"frame"`
	FullArt         bool     `json:"full_art"`
	Games           []string `json:"games"`
	HighresImage    bool     `json:"highres_image"`
	ID              string   `json:"id" bson:"_id"`
	IllustrationID  string   `json:"illustration_id"`
	ImageStatus     string   `json:"image_status"`
	ImageUris       struct {
		ArtCrop    string `json:"art_crop"`
		BorderCrop string `json:"border_crop"`
		Large      string `json:"large"`
		Normal     string `json:"normal"`
		Png        string `json:"png"`
		Small      string `json:"small"`
	} `json:"image_uris"`
	Keywords   []interface{} `json:"keywords"`
	Lang       string        `json:"lang"`
	Layout     string        `json:"layout"`
	Legalities struct {
		Alchemy         string `json:"alchemy"`
		Brawl           string `json:"brawl"`
		Commander       string `json:"commander"`
		Duel            string `json:"duel"`
		Future          string `json:"future"`
		Gladiator       string `json:"gladiator"`
		Historic        string `json:"historic"`
		Historicbrawl   string `json:"historicbrawl"`
		Legacy          string `json:"legacy"`
		Modern          string `json:"modern"`
		Oldschool       string `json:"oldschool"`
		Pauper          string `json:"pauper"`
		Paupercommander string `json:"paupercommander"`
		Penny           string `json:"penny"`
		Pioneer         string `json:"pioneer"`
		Premodern       string `json:"premodern"`
		Standard        string `json:"standard"`
		Vintage         string `json:"vintage"`
	} `json:"legalities"`
	ManaCost        string        `json:"mana_cost"`
	MultiverseIds   []interface{} `json:"multiverse_ids"`
	Name            string        `json:"name"`
	Nonfoil         bool          `json:"nonfoil"`
	Object          string        `json:"object"`
	OracleID        string        `json:"oracle_id"`
	OracleText      string        `json:"oracle_text"`
	Oversized       bool          `json:"oversized"`
	Prices          PriceEntry    `json:"prices"`
	PrintedName     string        `json:"printed_name"`
	PrintedText     string        `json:"printed_text"`
	PrintedTypeLine string        `json:"printed_type_line"`
	PrintsSearchURI string        `json:"prints_search_uri"`
	Promo           bool          `json:"promo"`
	PurchaseUris    struct {
		Cardhoarder string `json:"cardhoarder"`
		Cardmarket  string `json:"cardmarket"`
		Tcgplayer   string `json:"tcgplayer"`
	} `json:"purchase_uris"`
	Rarity      string `json:"rarity"`
	RelatedUris struct {
		Edhrec                    string `json:"edhrec"`
		Mtgtop8                   string `json:"mtgtop8"`
		TcgplayerInfiniteArticles string `json:"tcgplayer_infinite_articles"`
		TcgplayerInfiniteDecks    string `json:"tcgplayer_infinite_decks"`
	} `json:"related_uris"`
	ReleasedAt     string  `json:"released_at"`
	Reprint        bool    `json:"reprint"`
	Reserved       bool    `json:"reserved"`
	RulingsURI     string  `json:"rulings_uri"`
	ScryfallSetURI string  `json:"scryfall_set_uri"`
	ScryfallURI    string  `json:"scryfall_uri"`
	Set            string  `json:"set"`
	SetID          string  `json:"set_id"`
	SetName        string  `json:"set_name"`
	SetSearchURI   string  `json:"set_search_uri"`
	SetType        string  `json:"set_type"`
	SetURI         string  `json:"set_uri"`
	StorySpotlight bool    `json:"story_spotlight"`
	Textless       bool    `json:"textless"`
	TCGPlayerID    float64 `json:"tcgplayer_id"`
	TypeLine       string  `json:"type_line"`
	URI            string  `json:"uri"`
	Variation      bool    `json:"variation"`
}

type BulkIndex struct {
	Object  string `json:"object"`
	HasMore bool   `json:"has_more"`
	Data    []struct {
		Object          string    `json:"object"`
		ID              string    `json:"id"`
		Type            string    `json:"type"`
		UpdatedAt       time.Time `json:"updated_at"`
		URI             string    `json:"uri"`
		Name            string    `json:"name"`
		Description     string    `json:"description"`
		Size            int       `json:"size"`
		DownloadURI     string    `json:"download_uri"`
		ContentType     string    `json:"content_type"`
		ContentEncoding string    `json:"content_encoding"`
	} `json:"data"`
}

func makeRequest(url string) (*http.Response, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatalf("Error creating HTTP request: %v", err)
	}

	req.Header.Set("User-Agent", fmt.Sprintf("Serra_Collection_Tracker/%s", Version))
	req.Header.Set("Accept", "*/*")

	return http.DefaultClient.Do(req)
}

func fetchBulkDownloadURL() (string, error) {
	url := "https://api.scryfall.com/bulk-data"
	downloadURL := ""

	// Make an HTTP GET request
	resp, err := makeRequest(url)
	if err != nil {
		log.Fatalf("Error fetching data: %v", err)
	}
	defer resp.Body.Close()

	// Read the response body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatalf("Error reading response body: %v", err)
	}

	// Unmarshal the JSON response
	var bulkData BulkIndex
	if err := json.Unmarshal(body, &bulkData); err != nil {
		log.Fatalf("Error unmarshaling JSON: %v", err)
	}

	// Find and print the unique cards URL
	for _, item := range bulkData.Data {
		if item.Type == "default_cards" {
			downloadURL = item.DownloadURI
		}
	}

	return downloadURL, nil
}

func downloadBulkData(downloadURL string) (string, error) {

	// Create a temporary directory
	tempDir, err := os.MkdirTemp("", "download")
	if err != nil {
		log.Fatalf("Error creating temporary directory: %v", err)
	}
	// defer os.RemoveAll(tempDir) // Clean up the directory when done

	// Create a temporary file in the temporary directory
	tempFile, err := os.CreateTemp(tempDir, "downloaded-*.json") // Adjust the extension if necessary
	if err != nil {
		log.Fatalf("Error creating temporary file: %v", err)
	}
	// defer tempFile.Close() // Ensure we close the file when we're done

	// Download the file
	resp, err := makeRequest(downloadURL)
	if err != nil {
		log.Fatalf("Error downloading file: %v", err)
	}
	defer resp.Body.Close() // Make sure to close the response body

	// Check for a successful response
	if resp.StatusCode != http.StatusOK {
		log.Fatalf("Error: received status code %d", resp.StatusCode)
	}

	// Copy the response body to the temporary file
	_, err = io.Copy(tempFile, resp.Body)
	if err != nil {
		log.Fatalf("Error saving file: %v", err)
	}

	return tempFile.Name(), nil
}

func loadBulkFile(bulkFilePath string) ([]Card, error) {

	var cards []Card
	fileBytes, _ := os.ReadFile(bulkFilePath)
	defer os.Remove(bulkFilePath)

	err := json.Unmarshal(fileBytes, &cards)
	if err != nil {
		fmt.Println("Error unmarshalling bulk file:", err)
		return cards, nil
	}

	return cards, nil

}

func getCardFromBulk(cards []Card, setName, collectorNumber string) (*Card, error) {
	var foundCard Card
	for _, v := range cards {
		if v.CollectorNumber == collectorNumber && v.Set == setName {
			foundCard = v
			return &foundCard, nil
		}
	}
	return &Card{}, fmt.Errorf("Card %s/%s not found in bulk data", setName, collectorNumber)
}

type PriceEntry struct {
	Date      primitive.DateTime `bson:"date"`
	Eur       float64            `json:"eur,string" bson:"eur,float64"`
	EurFoil   float64            `json:"eur_foil,string" bson:"eur_foil,float64"`
	Tix       float64            `json:"tix,string" bson:"tix,float64"`
	Usd       float64            `json:"usd,string" bson:"usd,float64"`
	UsdEtched float64            `json:"usd_etched,string" bson:"usd_etched,float64"`
	UsdFoil   float64            `json:"usd_foil,string" bson:"usd_foil,float64"`
}

// Sets

type SetList struct {
	Data []Set `json:"data"`
}

type Set struct {
	SerraPrices  []PriceEntry       `bson:"serra_prices"`
	SerraCreated primitive.DateTime `bson:"serra_created"`
	SerraUpdated primitive.DateTime `bson:"serra_updated"`
	CardCount    int64              `json:"card_count" bson:"cardcount"`
	Code         string             `json:"code"`
	Digital      bool               `json:"digital"`
	FoilOnly     bool               `json:"foil_only"`
	IconSvgURI   string             `json:"icon_svg_uri"`
	ID           string             `json:"id" bson:"_id"`
	Name         string             `json:"name"`
	NonfoilOnly  bool               `json:"nonfoil_only"`
	Object       string             `json:"object"`
	ReleasedAt   string             `json:"released_at"`
	ScryfallURI  string             `json:"scryfall_uri"`
	SearchURI    string             `json:"search_uri"`
	SetType      string             `json:"set_type"`
	TcgplayerID  int64              `json:"tcgplayer_id"`
	URI          string             `json:"uri"`
}

// Getter for currency specific value
func (c Card) getValue(foil bool) float64 {
	if getCurrency() == EUR {
		if foil {
			return c.Prices.EurFoil
		}
		return c.Prices.Eur
	}
	if foil {
		return c.Prices.UsdFoil
	}
	return c.Prices.Usd
}

func fetchCard(setName, collectorNumber string) (*Card, error) {
	resp, err := makeRequest(fmt.Sprintf("https://api.scryfall.com/cards/%s/%s", setName, collectorNumber))
	if err != nil {
		log.Fatalln(err)
		return &Card{}, err
	}

	if resp.StatusCode != 200 {
		log.Println(resp.StatusCode)
		return &Card{}, fmt.Errorf("Card %s/%s not found", setName, collectorNumber)
	}

	//we read the response body on the line below.
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatalf("%s", err)
		return &Card{}, err
	}

	r := bytes.NewReader(body)
	decoder := json.NewDecoder(r)
	val := &Card{}
	err = decoder.Decode(val)
	if err != nil {
		log.Fatalf("%s", err)
	}

	// Set created Time
	val.SerraCreated = primitive.NewDateTimeFromTime(time.Now())

	// Increase Price
	val.Prices.Date = primitive.NewDateTimeFromTime(time.Now())
	val.SerraPrices = append(val.SerraPrices, val.Prices)

	imgResp, imgErr := makeRequest(val.ImageUris.Png)
	if imgErr != nil {
		log.Fatalln(err)
		return &Card{}, err
	}
	bytes, readErr := io.ReadAll(imgResp.Body)
	if readErr != nil {
		log.Fatalln(err)
		return &Card{}, err
	}

	val.SerraImage64 = base64.StdEncoding.EncodeToString(bytes)

	return val, nil
}

func fetchSets() (*SetList, error) {
	// TODO: better URL Building...
	resp, err := makeRequest("https://api.scryfall.com/sets")
	if err != nil {
		log.Fatalln(err)
		return &SetList{}, err
	}

	if resp.StatusCode != 200 {
		return &SetList{}, fmt.Errorf("/sets not found")
	}

	//We Read the response body on the line below.
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatalln(err)
		return &SetList{}, err
	}

	r := bytes.NewReader(body)
	decoder := json.NewDecoder(r)
	val := &SetList{}

	err = decoder.Decode(val)
	if err != nil {
		log.Fatalln(err)
	}

	return val, nil
}
