package manga

import (
	"bytes"
	"errors"
	"image"
	_ "image/jpeg" // Register JPEG format
	_ "image/png"  // Register PNG format
	"net/http"
	"seanime/internal/database/db"
	"seanime/internal/database/models"
	"seanime/internal/events"
	"seanime/internal/extension"
	"seanime/internal/util/filecache"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/rs/zerolog"
	_ "golang.org/x/image/bmp"  // Register BMP format
	_ "golang.org/x/image/tiff" // Register Tiff format
	_ "golang.org/x/image/webp" // Register WebP format
)

var (
	ErrNoResults            = errors.New("no results found for this media")
	ErrNoChapters           = errors.New("no manga chapters found")
	ErrChapterNotFound      = errors.New("chapter not found")
	ErrChapterNotDownloaded = errors.New("chapter not downloaded")
	ErrNoTitlesProvided     = errors.New("no titles provided")
)

type (
	Repository struct {
		logger                *zerolog.Logger
		fileCacher            *filecache.Cacher
		cacheDir              string
		providerExtensionBank *extension.UnifiedBank
		serverUri             string
		wsEventManager        events.WSEventManagerInterface
		mu                    sync.Mutex
		downloadDir           string
		db                    *db.Database

		settings *models.Settings
	}

	NewRepositoryOptions struct {
		Logger         *zerolog.Logger
		CacheDir       string
		FileCacher     *filecache.Cacher
		ServerURI      string
		WsEventManager events.WSEventManagerInterface
		DownloadDir    string
		Database       *db.Database
	}
)

func NewRepository(opts *NewRepositoryOptions) *Repository {
	r := &Repository{
		logger:                opts.Logger,
		fileCacher:            opts.FileCacher,
		cacheDir:              opts.CacheDir,
		serverUri:             opts.ServerURI,
		wsEventManager:        opts.WsEventManager,
		downloadDir:           opts.DownloadDir,
		providerExtensionBank: extension.NewUnifiedBank(),
		db:                    opts.Database,
	}
	return r
}

func (r *Repository) SetSettings(settings *models.Settings) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.settings = settings
}

func (r *Repository) InitExtensionBank(bank *extension.UnifiedBank) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.providerExtensionBank = bank
	r.logger.Debug().Msg("manga: Initialized provider extension bank")
}

func (r *Repository) RemoveProvider(id string) {
	r.providerExtensionBank.Delete(id)
}

func (r *Repository) GetProviderExtensionBank() *extension.UnifiedBank {
	return r.providerExtensionBank
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File Cache
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

type bucketType string

const (
	bucketTypeChapterKey                = "1"
	bucketTypeChapter        bucketType = "chapters"
	bucketTypePage           bucketType = "pages"
	bucketTypePageDimensions bucketType = "page-dimensions"
)

// getFcProviderBucket returns a bucket for the provider and mediaId.
//
//	e.g., manga_comick_chapters_123, manga_mangasee_pages_456
//
// Note: Each bucket contains only 1 key-value pair.
func (r *Repository) getFcProviderBucket(provider string, mediaId int, bucketType bucketType) filecache.Bucket {
	return filecache.NewBucket("manga_"+provider+"_"+string(bucketType)+"_"+strconv.Itoa(mediaId), time.Hour*24*7)
}

// EmptyMangaCache deletes all manga buckets associated with the given mediaId.
func (r *Repository) EmptyMangaCache(mediaId int) (err error) {
	// Empty the manga cache
	err = r.fileCacher.RemoveAllBy(func(filename string) bool {
		return strings.HasPrefix(filename, "manga_") && strings.Contains(filename, strconv.Itoa(mediaId))
	})
	return
}

func ParseChapterContainerFileName(filename string) (provider string, bucketType bucketType, mediaId int, ok bool) {
	filename = strings.TrimSuffix(filename, ".json")
	filename = strings.TrimSuffix(filename, ".cache")
	filename = strings.TrimSuffix(filename, ".txt")
	parts := strings.Split(filename, "_")
	if len(parts) != 4 {
		return "", "", 0, false
	}

	provider = parts[1]
	var err error
	mediaId, err = strconv.Atoi(parts[3])
	if err != nil {
		return "", "", 0, false
	}

	switch parts[2] {
	case "chapters":
		bucketType = bucketTypeChapter
	case "pages":
		bucketType = bucketTypePage
	case "page-dimensions":
		bucketType = bucketTypePageDimensions
	default:
		return "", "", 0, false
	}

	ok = true
	return
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

func getImageNaturalSize(url string) (int, int, error) {
	// Fetch the image
	resp, err := http.Get(url)
	if err != nil {
		return 0, 0, err
	}
	defer resp.Body.Close()

	// Decode the image
	img, _, err := image.DecodeConfig(resp.Body)
	if err != nil {
		return 0, 0, err
	}

	// Return the natural size
	return img.Width, img.Height, nil
}

func getImageNaturalSizeB(data []byte) (int, int, error) {
	// Decode the image
	img, _, err := image.DecodeConfig(bytes.NewReader(data))
	if err != nil {
		return 0, 0, err
	}

	// Return the natural size
	return img.Width, img.Height, nil
}
