package nixstore

import (
	"database/sql"
	"encoding/base64"
	"fmt"
	"path"
	"strings"

	"hg.lukegb.com/lukegb/depot/go/nix/nar/narinfo"

	_ "github.com/mattn/go-sqlite3"
)

const DefaultStoreDB = "/nix/var/nix/db/db.sqlite"

type DB struct {
	db *sql.DB
}

func (d *DB) NARInfo(storePath string) (*narinfo.NarInfo, error) {
	stmt, err := d.db.Prepare(`
SELECT
  vp.id,
  vp.path,
  vp.hash,
  vp.deriver,
  vp.narSize,
  vp.sigs,
1 FROM
  ValidPaths vp
WHERE 1=1
  AND vp.path = ?
`)
	if err != nil {
		return nil, fmt.Errorf("preparing initial statement: %w", err)
	}
	defer stmt.Close()

	ni := narinfo.NarInfo{}

	var storePathID int
	var dummy int
	var hashStr string
	var deriverStr *string
	var sigsStr *string
	err = stmt.QueryRow(storePath).Scan(
		&storePathID,
		&ni.StorePath,
		&hashStr,
		&deriverStr,
		&ni.NarSize,
		&sigsStr,
		&dummy)
	if err != nil {
		return nil, fmt.Errorf("scanning initial statement: %w", err)
	}

	ni.NarHash, err = narinfo.HashFromString(hashStr)
	if err != nil {
		return nil, fmt.Errorf("parsing hash %q: %w", hashStr, err)
	}

	if deriverStr != nil {
		ni.Deriver = path.Base(*deriverStr)
	}
	if ni.Deriver == "." {
		ni.Deriver = ""
	}

	if sigsStr != nil {
		sigsBits := strings.Fields(*sigsStr)
		sigs := make(map[string][]byte)
		for _, sigsBit := range sigsBits {
			sigsPieces := strings.Split(sigsBit, ":")
			if len(sigsPieces) != 2 {
				return nil, fmt.Errorf("parsing signature %q: wrong number of : separated pieces (%d)", sigsBit, len(sigsPieces))
			}
			var err error
			sigs[sigsPieces[0]], err = base64.StdEncoding.DecodeString(sigsPieces[1])
			if err != nil {
				return nil, fmt.Errorf("parsing signature %q: invalid base64: %w", sigsBit, err)
			}
		}
		ni.Sig = sigs
	}

	referencesStmt, err := d.db.Prepare(`
SELECT
	refedvp.path
FROM
	Refs r
INNER JOIN
	ValidPaths refedvp ON refedvp.id = r.reference
WHERE
	r.referrer = ?
ORDER BY 1
`)
	if err != nil {
		return nil, fmt.Errorf("preparing references statement: %w", err)
	}
	defer referencesStmt.Close()

	referencesRows, err := referencesStmt.Query(storePathID)
	if err != nil {
		return nil, fmt.Errorf("querying references: %w", err)
	}
	defer referencesRows.Close()

	for referencesRows.Next() {
		var refStorePath string
		if err := referencesRows.Scan(&refStorePath); err != nil {
			return nil, fmt.Errorf("scanning references: %w", err)
		}
		ni.References = append(ni.References, path.Base(refStorePath))
	}

	return &ni, nil
}

func (d *DB) Close() error {
	return d.db.Close()
}

func OpenDB(dbPath string) (*DB, error) {
	sqlDB, err := sql.Open("sqlite3", dbPath)
	if err != nil {
		return nil, err
	}
	return &DB{
		db: sqlDB,
	}, nil
}