package main

import (
	"fmt"
	"github.com/d2fn/sumi/internal/ids"
	"github.com/d2fn/sumi/internal/ora"
	"github.com/go-git/go-git/v6"
	"log"
	//"github.com/go-git/go-git/v5/plumbing"
	"database/sql"
	"github.com/gen2brain/raylib-go/raylib"
	"github.com/go-git/go-git/v6/plumbing/object"
	_ "modernc.org/sqlite" // pure Go, Nix-friendly
	"os"
	"path/filepath"
	"time"
)

type Storage struct {
	repoRoot     string
	snapshotsDir string
	gen          *ids.Generator
	db           *sql.DB
	log          *log.Logger
}

func NewStorage(snapshotsDir string) (*Storage, error) {
	log := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
	gen, _ := ids.NewGenerator(82)
	db, err := OpenDB(filepath.Join(snapshotsDir, "snapshots.db"), log)
	if err != nil {
		return nil, err
	}
	s := Storage{
		repoRoot:     ".",
		snapshotsDir: snapshotsDir,
		gen:          gen,
		db:           db,
		log:          log,
	}
	return &s, nil
}

func OpenDB(path string, log *log.Logger) (*sql.DB, error) {
	log.Printf("Opening sqlite db")
	first := false
	if _, err := os.Stat(path); os.IsNotExist(err) {
		log.Printf("Database not found, initializing")
		first = true
	}

	db, err := sql.Open("sqlite", path)
	if err != nil {
		log.Printf("Error opening database at %s", path)
		return nil, err
	}

	if first {
		log.Printf("Initializing empty db with schema", path)
		if err := initSchema(db); err != nil {
			db.Close()
			return nil, err
		}
		log.Printf("Error initializing schema: %v", err)
	}

	return db, nil
}

func initSchema(db *sql.DB) error {
	schema, err := os.ReadFile("db/schema.sql")
	if err != nil {
		return err
	}

	tx, err := db.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	if _, err := tx.Exec(string(schema)); err != nil {
		return err
	}

	return tx.Commit()
}

func (s *Storage) Save(capture *SketchCapture) (string, error) {

	id, _ := s.gen.Next()
	flakeId := ids.Base62Encode(id)
	path := filepath.Join(s.snapshotsDir, flakeId)
	dataDir := filepath.Join(path, "data")
	os.MkdirAll(dataDir, 0755)

	hash, branch, committed, err := s.SaveToGit(flakeId)

	if err != nil {
		s.log.Printf("Error getting working tree in a known clean state: %v", err)
	} else {
		if committed {
			s.log.Printf("Created commit %s on %s for snapshot %s", hash, branch, flakeId)
		} else {
			s.log.Printf("Referencing commit %s on %s for snapshot %s", hash, branch, flakeId)
		}
	}

	err = s.SaveToDb(id, flakeId, branch, hash, path, committed)
	if err != nil {
		s.log.Printf("Error writing to db: %v\n", err)
	}

	// wysiwyg at screen res
	img := rl.LoadImageFromScreen()
	defer rl.UnloadImage(img)

	snapshotPng := filepath.Join(path, fmt.Sprintf("%s-screen.png", flakeId))
	rl.ExportImage(*img, snapshotPng)

	// capture full res compsite
	compositePng := filepath.Join(path, fmt.Sprintf("%s-final.png", flakeId))
	rl.ExportImage(*capture.compositeImage, compositePng)

	// capture full res layer
	oraLayers := make([]ora.ORALayer, len(capture.layerToolsOrdered))
	for i, layerTools := range capture.layerToolsOrdered {
		filename := fmt.Sprintf("%s-%03d.png", flakeId, i)
		layerPng := filepath.Join(path, "data", filename)
		rl.ExportImage(*layerTools.capture, layerPng)
		opacity := float32(layerTools.config.a) / 255.0
		ii := len(capture.layerTools) - 1 - i
		oraLayers[ii] =
			ora.ORALayer{
				Name:     layerTools.name,
				Filename: filename,
				Visible:  layerTools.config.visible,
				Opacity:  opacity,
				Blend:    "svg:src-over",
			}
	}

	oraPath := filepath.Join(path, fmt.Sprintf("%s-layers.ora", flakeId))

	ora.WriteORA(oraPath, int(capture.width), int(capture.height), oraLayers,
		func(name string) ([]byte, error) {
			return os.ReadFile(filepath.Join(path, "data", name))
		})

	s.log.Printf("Saved snapshot to %s\n", path)

	return path, nil
}

func (s *Storage) SaveToGit(flakeId string) (string, string, bool, error) {
	s.log.Printf("Checking git status...\n")
	return CommitAllIfDirty(s.repoRoot, "automated snapshot", s.log)
}

func (s *Storage) SaveToDb(id uint64, flakeId string, branch string, hash string, path string, committed bool) error {

	_, err := s.db.Exec(`
		INSERT INTO snapshots (id, sid, created_at, branch, git_hash, committed, path)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`,
		id,
		flakeId,
		time.Now().UnixMilli(),
		branch,
		hash,
		committed,
		path,
	)

	if err != nil {
		s.log.Printf("Error inserting snapshot row into db: %v\n", err)
	}

	return err
}

func HeadHash(repoPath string) (string, error) {
	r, err := git.PlainOpen(repoPath)
	if err != nil {
		return "", err
	}

	ref, err := r.Head()
	if err != nil {
		return "", err
	}

	return ref.Hash().String(), nil
}

func IsDirty(repoPath string) (bool, error) {
	r, err := git.PlainOpen(repoPath)
	if err != nil {
		return false, err
	}

	wt, err := r.Worktree()
	if err != nil {
		return false, err
	}

	status, err := wt.Status()
	if err != nil {
		return false, err
	}

	return !status.IsClean(), nil
}

func CommitAllIfDirty(repoPath, message string, log *log.Logger) (commitHash string, branch string, committed bool, err error) {
	r, err := git.PlainOpen(repoPath)
	if err != nil {
		log.Printf("Error opening git repo")
		return
	}

	// Determine branch (may be empty if detached)
	ref, err := r.Head()
	if err != nil {
		log.Printf("Error determining head commit")
		return
	}
	if ref.Name().IsBranch() {
		branch = ref.Name().Short()
	}

	wt, err := r.Worktree()
	if err != nil {
		log.Printf("Error getting worktree state")
		return
	}

	status, err := wt.Status()
	if err != nil {
		log.Printf("Error getting worktree status")
		return
	}

	if status.IsClean() {
		hash, err := HeadHash(repoPath)
		if err != nil {
			log.Printf("Repo was clean but there was an error checking the commit for HEAD: %v", err)
		}
		return hash, branch, false, nil
	}

	// Stage everything (git add -A)
	if err = wt.AddWithOptions(&git.AddOptions{All: true}); err != nil {
		log.Printf("Error adding git changes to index")
		return
	}

	hash, err := wt.Commit(message, &git.CommitOptions{
		Author: &object.Signature{
			Name:  "sumi",
			Email: "sumi@local",
			When:  time.Now(),
		},
	})
	if err != nil {
		log.Printf("Error creating commit")
		return
	}

	return hash.String(), branch, true, nil
}
