package sqlite

import (
	"context"
	"fmt"
	"strings"
)

type Pack struct {
	ID             int64
	PackID         string
	Version        string
	Checksum       string
	Vendor         string
	TargetHost     string
	MinHostVersion string
	MaxHostVersion string
	ManifestJSON   string
}

type PacksRepo struct {
	db execQuerier
}

func newPacksRepo(db execQuerier) *PacksRepo {
	return &PacksRepo{db: db}
}

func (r *PacksRepo) GetByID(ctx context.Context, id int64) (Pack, error) {
	if id <= 0 {
		return Pack{}, fmt.Errorf("id is required")
	}

	var pack Pack
	if err := r.db.QueryRowContext(ctx, `SELECT id, pack_id, version, checksum, vendor, target_host, min_host_version, max_host_version, manifest_json FROM packs WHERE id = ?`, id).Scan(
		&pack.ID,
		&pack.PackID,
		&pack.Version,
		&pack.Checksum,
		&pack.Vendor,
		&pack.TargetHost,
		&pack.MinHostVersion,
		&pack.MaxHostVersion,
		&pack.ManifestJSON,
	); err != nil {
		return Pack{}, err
	}
	return pack, nil
}

func (r *PacksRepo) GetByPackID(ctx context.Context, packID string) (Pack, error) {
	packID = strings.TrimSpace(packID)
	if packID == "" {
		return Pack{}, fmt.Errorf("pack_id is required")
	}

	var pack Pack
	if err := r.db.QueryRowContext(ctx, `SELECT id, pack_id, version, checksum, vendor, target_host, min_host_version, max_host_version, manifest_json FROM packs WHERE pack_id = ?`, packID).Scan(
		&pack.ID,
		&pack.PackID,
		&pack.Version,
		&pack.Checksum,
		&pack.Vendor,
		&pack.TargetHost,
		&pack.MinHostVersion,
		&pack.MaxHostVersion,
		&pack.ManifestJSON,
	); err != nil {
		return Pack{}, err
	}
	return pack, nil
}

func (r *PacksRepo) Create(ctx context.Context, pack Pack) (int64, error) {
	packID := strings.TrimSpace(pack.PackID)
	version := strings.TrimSpace(pack.Version)
	checksum := strings.TrimSpace(pack.Checksum)
	manifestJSON := strings.TrimSpace(pack.ManifestJSON)
	if manifestJSON == "" {
		manifestJSON = "{}"
	}

	switch {
	case packID == "":
		return 0, fmt.Errorf("pack_id is required")
	case version == "":
		return 0, fmt.Errorf("version is required")
	case checksum == "":
		return 0, fmt.Errorf("checksum is required")
	}

	result, err := r.db.ExecContext(
		ctx,
		`INSERT INTO packs (pack_id, version, checksum, vendor, target_host, min_host_version, max_host_version, manifest_json)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
		packID,
		version,
		checksum,
		strings.TrimSpace(pack.Vendor),
		strings.TrimSpace(pack.TargetHost),
		strings.TrimSpace(pack.MinHostVersion),
		strings.TrimSpace(pack.MaxHostVersion),
		manifestJSON,
	)
	if err != nil {
		return 0, fmt.Errorf("insert pack %q: %w", packID, err)
	}

	id, err := result.LastInsertId()
	if err != nil {
		return 0, fmt.Errorf("read inserted pack id for %q: %w", packID, err)
	}
	return id, nil
}

func (r *PacksRepo) ListAll(ctx context.Context) ([]Pack, error) {
	rows, err := r.db.QueryContext(ctx, `SELECT id, pack_id, version, checksum, vendor, target_host, min_host_version, max_host_version, manifest_json FROM packs ORDER BY id`)
	if err != nil {
		return nil, fmt.Errorf("list packs: %w", err)
	}
	defer rows.Close()

	var packs []Pack
	for rows.Next() {
		var pack Pack
		if err := rows.Scan(
			&pack.ID,
			&pack.PackID,
			&pack.Version,
			&pack.Checksum,
			&pack.Vendor,
			&pack.TargetHost,
			&pack.MinHostVersion,
			&pack.MaxHostVersion,
			&pack.ManifestJSON,
		); err != nil {
			return nil, fmt.Errorf("scan pack: %w", err)
		}
		packs = append(packs, pack)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate packs: %w", err)
	}
	return packs, nil
}

func (r *PacksRepo) Upsert(ctx context.Context, pack Pack) (int64, error) {
	packID := strings.TrimSpace(pack.PackID)
	version := strings.TrimSpace(pack.Version)
	checksum := strings.TrimSpace(pack.Checksum)
	manifestJSON := strings.TrimSpace(pack.ManifestJSON)
	if manifestJSON == "" {
		manifestJSON = "{}"
	}

	switch {
	case packID == "":
		return 0, fmt.Errorf("pack_id is required")
	case version == "":
		return 0, fmt.Errorf("version is required")
	case checksum == "":
		return 0, fmt.Errorf("checksum is required")
	}

	result, err := r.db.ExecContext(
		ctx,
		`INSERT INTO packs (pack_id, version, checksum, vendor, target_host, min_host_version, max_host_version, manifest_json)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?)
		ON CONFLICT(pack_id) DO UPDATE SET
			version = excluded.version,
			checksum = excluded.checksum,
			vendor = excluded.vendor,
			target_host = excluded.target_host,
			min_host_version = excluded.min_host_version,
			max_host_version = excluded.max_host_version,
			manifest_json = excluded.manifest_json`,
		packID,
		version,
		checksum,
		strings.TrimSpace(pack.Vendor),
		strings.TrimSpace(pack.TargetHost),
		strings.TrimSpace(pack.MinHostVersion),
		strings.TrimSpace(pack.MaxHostVersion),
		manifestJSON,
	)
	if err != nil {
		return 0, fmt.Errorf("upsert pack %q: %w", packID, err)
	}

	id, err := result.LastInsertId()
	if err == nil && id > 0 {
		return id, nil
	}
	persisted, getErr := r.GetByPackID(ctx, packID)
	if getErr != nil {
		if err != nil {
			return 0, fmt.Errorf("read upserted pack %q: %w", packID, getErr)
		}
		return 0, getErr
	}
	return persisted.ID, nil
}
