/*
 * Copyright 2026 Oleg Borodin <onborodin@gmail.com>
 */
package storage

import (
	"bytes"
	"errors"
	"io"
	"os"
	"path/filepath"
)

const (
	upsubdir   = "uploads"
	blobsubdir = "blobs"
)

func (store *Storage) makeUppath(upname string) string {
	return filepath.Join(store.basepath, upsubdir, upname) + ".bin"
}

func (store *Storage) makeUpsubdir() string {
	return filepath.Join(store.basepath, upsubdir)
}

func (store *Storage) makeBlobpath(name, digstr string) string {
	return filepath.Join(store.basepath, blobsubdir, name, digstr) + ".bin"
}

func (store *Storage) makeBlobsubdir(name string) string {
	return filepath.Join(store.basepath, blobsubdir, name)
}

func (store *Storage) WriteUpload(upID string, source io.Reader) (int64, error) {
	var err error
	var recsize int64

	uploadDir := store.makeUpsubdir()
	_, err = os.Stat(uploadDir)
	if os.IsNotExist(err) {
		err = os.MkdirAll(uploadDir, 0750)
		if err != nil {
			return recsize, err
		}
	}
	if err != nil {
		return recsize, err
	}
	upPath := store.makeUppath(upID)
	upFile, err := os.OpenFile(upPath, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return recsize, err
	}
	defer upFile.Close()
	recsize, err = io.Copy(upFile, source)
	if err != nil {
		return recsize, err
	}
	return recsize, err
}

func (store *Storage) LinkUpload(reference, name, digest string) error {
	var err error
	upPath := store.makeUppath(reference)

	blobdir := store.makeBlobsubdir(name)
	_, err = os.Stat(blobdir)
	if errors.Is(err, os.ErrNotExist) {
		err = os.MkdirAll(blobdir, 0750)
		if err != nil {
			return err
		}
	}
	if err != nil {
		return err
	}
	blobPath := store.makeBlobpath(name, digest)
	_, err = os.Stat(blobPath)
	if err == nil {
		err = os.Remove(blobPath)
		if err != nil {
			return err
		}
	}
	if os.IsNotExist(err) {
		err = nil
	}
	if err != nil {
		return err
	}
	err = os.Link(upPath, blobPath)
	if err != nil {
		return err
	}
	err = os.Remove(upPath)
	if err != nil {
		return err
	}
	return err
}

func (store *Storage) RemoveUpload(digest string) error {
	var err error
	upPath := store.makeUppath(digest)
	err = os.Remove(upPath)
	if err != nil {
		return err
	}
	return err
}

func (st *Storage) UploadExists(name, reference string) (bool, int64, error) {
	var err error
	var size int64
	upPath := st.makeUppath(reference)
	fileStat, err := os.Stat(upPath)
	if errors.Is(err, os.ErrNotExist) {
		return false, 0, nil
	}
	if err != nil {
		return false, 0, err
	}
	size = fileStat.Size()
	return true, size, err
}

func (store *Storage) WriteBlob(name, digstr string, source io.Reader) (int64, error) {
	var err error
	var size int64
	blobDir := store.makeBlobsubdir(name)
	_, err = os.Stat(blobDir)
	if os.IsNotExist(err) {
		err = os.MkdirAll(blobDir, 0750)
		if err != nil {
			return size, err
		}
	}
	if err != nil {
		return size, err
	}
	blobPath := store.makeBlobpath(name, digstr)
	blobFile, err := os.OpenFile(blobPath, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return size, err
	}
	defer blobFile.Close()
	size, err = io.Copy(blobFile, source)
	if err != nil {
		return size, err
	}
	return size, err
}

func (st *Storage) BlobExists(name, digest string) (bool, int64, error) {
	var err error
	var size int64

	blobPath := st.makeBlobpath(name, digest)
	fileStat, err := os.Stat(blobPath)
	if os.IsNotExist(err) {
		return false, 0, nil
	}
	if err != nil {
		return false, 0, err
	}
	size = fileStat.Size()
	return true, size, err
}

func (store *Storage) BlobReader(name, digest string) (int64, io.ReadCloser, error) {
	var err error
	var size int64
	blobPath := store.makeBlobpath(name, digest)
	nop := io.NopCloser(bytes.NewReader(nil))
	file, err := os.OpenFile(blobPath, os.O_RDONLY, 0)
	if err != nil {
		return size, nop, err
	}
	defer func() {
		if err != nil {
			file.Close()
		}
	}()
	filestat, err := file.Stat()
	if err != nil {
		return size, nop, err
	}
	size = filestat.Size()
	return size, file, err
}

func (store *Storage) DeleteBlob(name, digest string) error {
	var err error
	blobPath := store.makeBlobpath(name, digest)
	err = os.Remove(blobPath)
	if os.IsNotExist(err) {
		err = nil
	}
	if err != nil {
		return err
	}
	// TODO: remove dirs
	return err
}
