package photosstore

import (
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/gin-gonic/gin"
)

var (
	ErrStoreChunkAlreadyExists = errors.New("chunk file already exists")
)

type Store struct {
	Path string
}

type Chunk struct {
	*Store
	Sum   string
	Bytes []byte
}

func (s *Store) NewChunk(b []byte) *Chunk {
	sum := sha1.New()
	sum.Write(b)
	sumString := hex.EncodeToString(sum.Sum(nil))

	return &Chunk{s, sumString, b}
}

func (s *Store) LoadChunk(sum string) (*Chunk, error) {
	c := s.Chunk(sum)
	if !c.FileExists() {
		return nil, fmt.Errorf("chunk %s doesn't exists", sum)
	}
	b, err := os.ReadFile(c.Filename())
	if err != nil {
		return nil, err
	}
	c.Bytes = b
	return c, nil
}

func (s *Store) Chunk(sum string) *Chunk {
	return &Chunk{s, sum, nil}
}

func (c *Chunk) Dir() string {
	return filepath.Join(c.Path, "storage", c.Sum[0:1], c.Sum[1:2])
}

func (c *Chunk) Filename() string {
	return filepath.Join(c.Dir(), c.Sum)
}

func (c *Chunk) Size() int64 {
	st, err := os.Stat(c.Filename())
	if err != nil {
		return -1
	}
	return st.Size()
}

func (c *Chunk) FileExists() bool {
	fs, err := os.Stat(c.Filename())
	if errors.Is(err, os.ErrNotExist) {
		return false
	}
	return !fs.IsDir()
}

func (c *Chunk) Mkdir() error {
	return os.MkdirAll(c.Dir(), 0755)
}

func (c *Chunk) Save(login string) error {
	if c.FileExists() {
		return ErrStoreChunkAlreadyExists
	}

	if err := c.Mkdir(); err != nil {
		return err
	}

	if err := os.WriteFile(c.Filename(), c.Bytes, 0666); err != nil {
		return err
	}

	meta, err := os.Create(c.Filename() + ".json")
	if err != nil {
		return err
	}
	enc := json.NewEncoder(meta)
	enc.SetIndent("", "  ")
	return enc.Encode(gin.H{
		"author":   login,
		"date":     time.Now().UTC().Format("2006-01-02 15:04:05"),
		"checksum": c.Sum,
		"size":     len(c.Bytes),
	})
}

func (s *Store) Combine(sumb []string) (string, uint64, error) {
	sum := sha1.New()
	size := uint64(0)
	for _, sb := range sumb {
		c, err := s.LoadChunk(sb)
		if err != nil {
			return "", 0, err
		}
		sum.Write(c.Bytes)
		size += uint64(len(c.Bytes))
	}
	return hex.EncodeToString(sum.Sum(nil)), size, nil
}