package registry

import (
	"crypto/sha256"
	"fmt"
	"time"
)

// timestampRecord holds preserved timestamps and hash for an existing entry.
type timestampRecord struct {
	CreatedAt   time.Time
	UpdatedAt   time.Time
	ContentHash string
}

// ComputeFunctionHash computes a deterministic hash of all content fields of a Function
// (excluding created_at, updated_at, and content_hash itself).
func ComputeFunctionHash(f *Function) string {
	h := sha256.New()
	fmt.Fprintf(h, "%s|%s|%s|%s|%s|%s|%s|%s|%s",
		f.ID, f.Name, f.Kind, f.Lang, f.Domain, f.Version, f.Purity, f.Signature, f.Description)
	fmt.Fprintf(h, "|%s", marshalStrings(f.Tags))
	fmt.Fprintf(h, "|%s", marshalStrings(f.UsesFunctions))
	fmt.Fprintf(h, "|%s", marshalStrings(f.UsesTypes))
	fmt.Fprintf(h, "|%s", marshalStrings(f.Returns))
	fmt.Fprintf(h, "|%t|%s", f.ReturnsOptional, f.ErrorType)
	fmt.Fprintf(h, "|%s", marshalStrings(f.Imports))
	fmt.Fprintf(h, "|%s|%t", f.Example, f.Tested)
	fmt.Fprintf(h, "|%s", marshalStrings(f.Tests))
	fmt.Fprintf(h, "|%s|%s", f.TestFilePath, f.FilePath)
	fmt.Fprintf(h, "|%s", marshalProps(f.Props))
	fmt.Fprintf(h, "|%s", marshalStrings(f.Emits))
	if f.HasState != nil {
		fmt.Fprintf(h, "|%t", *f.HasState)
	}
	fmt.Fprintf(h, "|%s", f.Framework)
	fmt.Fprintf(h, "|%s", marshalStrings(f.Variant))
	fmt.Fprintf(h, "|%s|%s|%s", f.Notes, f.Documentation, f.Code)
	fmt.Fprintf(h, "|%s|%s|%s", f.SourceRepo, f.SourceLicense, f.SourceFile)
	fmt.Fprintf(h, "|%s", f.ParamsSchema)
	return fmt.Sprintf("%x", h.Sum(nil))
}

// ComputeTypeHash computes a deterministic hash of all content fields of a Type.
func ComputeTypeHash(t *Type) string {
	h := sha256.New()
	fmt.Fprintf(h, "%s|%s|%s|%s|%s|%s|%s|%s",
		t.ID, t.Name, t.Lang, t.Domain, t.Version, t.Algebraic, t.Definition, t.Description)
	fmt.Fprintf(h, "|%s", marshalStrings(t.Tags))
	fmt.Fprintf(h, "|%s", marshalStrings(t.UsesTypes))
	fmt.Fprintf(h, "|%s|%s|%s|%s|%s", t.FilePath, t.Examples, t.Notes, t.Documentation, t.Code)
	fmt.Fprintf(h, "|%s|%s|%s", t.SourceRepo, t.SourceLicense, t.SourceFile)
	return fmt.Sprintf("%x", h.Sum(nil))
}

// ComputeAppHash computes a deterministic hash of all content fields of an App.
func ComputeAppHash(a *App) string {
	h := sha256.New()
	fmt.Fprintf(h, "%s|%s|%s|%s|%s",
		a.ID, a.Name, a.Lang, a.Domain, a.Description)
	fmt.Fprintf(h, "|%s", marshalStrings(a.Tags))
	fmt.Fprintf(h, "|%s", marshalStrings(a.UsesFunctions))
	fmt.Fprintf(h, "|%s", marshalStrings(a.UsesTypes))
	fmt.Fprintf(h, "|%s", marshalStrings(a.UsesModules))
	fmt.Fprintf(h, "|%s|%s|%s|%s|%s|%s", a.Framework, a.EntryPoint, a.Documentation, a.Notes, a.DirPath, a.RepoURL)
	return fmt.Sprintf("%x", h.Sum(nil))
}

// ComputeAnalysisHash computes a deterministic hash of all content fields of an Analysis.
func ComputeAnalysisHash(a *Analysis) string {
	h := sha256.New()
	fmt.Fprintf(h, "%s|%s|%s|%s|%s",
		a.ID, a.Name, a.Lang, a.Domain, a.Description)
	fmt.Fprintf(h, "|%s", marshalStrings(a.Tags))
	fmt.Fprintf(h, "|%s", marshalStrings(a.UsesFunctions))
	fmt.Fprintf(h, "|%s", marshalStrings(a.UsesTypes))
	fmt.Fprintf(h, "|%s", marshalStrings(a.UsesModules))
	fmt.Fprintf(h, "|%s|%s|%s|%s|%s|%s", a.Framework, a.EntryPoint, a.Documentation, a.Notes, a.DirPath, a.RepoURL)
	return fmt.Sprintf("%x", h.Sum(nil))
}

// ComputeModuleHash computes a deterministic hash of all content fields of a Module.
func ComputeModuleHash(m *Module) string {
	h := sha256.New()
	fmt.Fprintf(h, "%s|%s|%s|%s|%s",
		m.ID, m.Name, m.Version, m.Lang, m.Description)
	fmt.Fprintf(h, "|%s", marshalStrings(m.Members))
	fmt.Fprintf(h, "|%s", marshalStrings(m.Tags))
	fmt.Fprintf(h, "|%s|%s|%s|%s", m.DirPath, m.RepoURL, m.Documentation, m.Notes)
	return fmt.Sprintf("%x", h.Sum(nil))
}

// ComputeProjectHash computes a deterministic hash of all content fields of a Project.
func ComputeProjectHash(p *Project) string {
	h := sha256.New()
	fmt.Fprintf(h, "%s|%s|%s",
		p.ID, p.Name, p.Description)
	fmt.Fprintf(h, "|%s", marshalStrings(p.Tags))
	fmt.Fprintf(h, "|%s|%s|%s|%s", p.RepoURL, p.DirPath, p.Documentation, p.Notes)
	return fmt.Sprintf("%x", h.Sum(nil))
}

// ComputeVaultHash computes a deterministic hash of all content fields of a Vault.
func ComputeVaultHash(v *Vault) string {
	h := sha256.New()
	fmt.Fprintf(h, "%s|%s|%s|%s|%s|%t",
		v.ID, v.Name, v.ProjectID, v.Description, v.Path, v.Symlink)
	fmt.Fprintf(h, "|%s", marshalStrings(v.Tags))
	return fmt.Sprintf("%x", h.Sum(nil))
}

// LoadTimestamps reads existing id → {created_at, updated_at, content_hash} from all tables.
// Called before Purge so we can preserve dates across reindexing.
func (db *DB) LoadTimestamps() (funcs, types, apps, analysis, projects, vaults, modules map[string]timestampRecord, err error) {
	funcs, err = loadTable(db, "functions")
	if err != nil {
		return
	}
	types, err = loadTable(db, "types")
	if err != nil {
		return
	}
	apps, err = loadTable(db, "apps")
	if err != nil {
		return
	}
	analysis, err = loadTable(db, "analysis")
	if err != nil {
		return
	}
	projects, err = loadTable(db, "projects")
	if err != nil {
		return
	}
	vaults, err = loadTable(db, "vaults")
	if err != nil {
		return
	}
	modules, err = loadTable(db, "modules")
	return
}

func loadTable(db *DB, table string) (map[string]timestampRecord, error) {
	rows, err := db.conn.Query(fmt.Sprintf("SELECT id, created_at, updated_at, content_hash FROM %s", table))
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	m := make(map[string]timestampRecord)
	for rows.Next() {
		var id, ca, ua, ch string
		if err := rows.Scan(&id, &ca, &ua, &ch); err != nil {
			return nil, err
		}
		rec := timestampRecord{ContentHash: ch}
		rec.CreatedAt, _ = time.Parse(time.RFC3339, ca)
		rec.UpdatedAt, _ = time.Parse(time.RFC3339, ua)
		m[id] = rec
	}
	return m, rows.Err()
}
