package blockchainlib

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/fatih/color"
)

type Block struct {
	Hash         string    `json:"hash"`
	Height       int64     `json:"height"`
	Date         time.Time `json:"date"`
	PreviousHash string    `json:"previoushash"`
	NextHash     string    `json:"nexthash"`
	Data         []string  `json:"data"`
	Emitter      string    `json:"emitter"` //the ID of the peer that has emmited the block
}

type Blockchain struct {
	GenesisBlock string    `json:"genesisblock"`
	LastUpdate   time.Time `json:"lastupdate"`
	Blocks       []Block   `json:"blocks"`
}

//var blockchain Blockchain

func (bc *Blockchain) GetBlockByHash(hash string) (Block, error) {
	for _, block := range bc.Blocks {
		if block.Hash == hash {
			return block, nil
		}
	}
	var b Block
	return b, errors.New("Block Hash not found")
}

func (bc *Blockchain) CreateBlock(data string) Block {
	var b Block
	b.Height = int64(len(bc.Blocks))
	if len(bc.Blocks) == 0 {
		b.Height = 0
	} else {
		b.PreviousHash = bc.Blocks[len(bc.Blocks)-1].Hash
	}
	b.Date = time.Now()
	b.Data = append(b.Data, data)
	//b.Emitter = runningPeer.ID

	b.Hash = HashBlock(b)
	return b
}

func (bc *Blockchain) BlockExists(block Block) bool {
	for _, b := range bc.Blocks {
		if b.Hash == block.Hash {
			return true
		}
	}
	return false
}

func (bc *Blockchain) AddBlock(block Block) error {
	if bc.BlockExists(block) {
		return errors.New("[Error adding Block]: Block already exists in the Blockchain")
	}
	if len(bc.Blocks) > 0 {
		bc.Blocks[len(bc.Blocks)-1].NextHash = block.Hash
	} else {
		bc.GenesisBlock = block.Hash
	}
	bc.Blocks = append(bc.Blocks, block)

	bc.SaveToDisk()

	return nil
}

func (bc *Blockchain) ReconstructBlockchainFromBlock(urlAPI string, h string) {
	color.Yellow("reconstructing the blockchain from last block in memory")
	var block Block
	var err error

	block, err = bc.GetBlockByHash(h)
	check(err)

	if h == "" {
		//no genesis block yet
		color.Green(urlAPI + "/blocks/genesis")
		res, err := http.Get(urlAPI + "/blocks/genesis")
		check(err)
		body, err := ioutil.ReadAll(res.Body)
		check(err)
		err = json.Unmarshal(body, &block)
		check(err)
		color.Yellow("[New Block]: " + block.Hash)
		err = bc.AddBlock(block)
		check(err)
	} else {
		block.NextHash = h
	}

	for block.NextHash != "" && block.Hash != "" {
		res, err := http.Get(urlAPI + "/blocks/next/" + block.Hash)
		check(err)
		body, err := ioutil.ReadAll(res.Body)
		check(err)
		err = json.Unmarshal(body, &block)
		check(err)
		if block.Hash != "" {
			color.Yellow("[New Block]: " + block.Hash)
			err = bc.AddBlock(block)
			check(err)
		}
	}
	bc.Print()
}

func (bc *Blockchain) Print() {
	color.Green("Printing Blockchain stored in memory")
	color.Green("Genesis Block: " + bc.GenesisBlock)
	for _, b := range bc.Blocks {
		color.Green("Block height:")
		fmt.Println(b.Height)
		color.Green("Hash: " + b.Hash)
		color.Green("Date: " + b.Date.String())
		color.Green("---")
	}
}

func (bc *Blockchain) ReadFromDisk() error {
	file, err := ioutil.ReadFile("blockchain.data")
	if err != nil {
		return err
	}
	content := string(file)
	json.Unmarshal([]byte(content), &bc)
	return nil
}

func (bc *Blockchain) SaveToDisk() error {
	bytesBlockchain, err := json.Marshal(bc)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile("blockchain.data", bytesBlockchain, 0644)
	if err != nil {
		return err
	}
	return nil
}
