package item

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	"github.com/jackc/pgx/v5/pgtype"
)

// Nullable creates a sql.Null[T] from a pointer.
func Nullable[T any](v *T) sql.Null[T] {
	if v == nil {
		return sql.Null[T]{}
	}
	return sql.Null[T]{V: *v, Valid: true}
}

// Ptr returns a pointer from sql.Null[T], or nil if not valid.
func Ptr[T any](n sql.Null[T]) *T {
	if !n.Valid {
		return nil
	}
	return &n.V
}

type Row struct {
	ID            int64
	PubID         string
	Title         sql.Null[string]
	Description   sql.Null[string]
	LinkURL       sql.Null[string]
	ItemType      string // 'image', 'video', 'quote', 'embed'
	EmbedProvider sql.Null[string]
	EmbedVideoID  sql.Null[string]
	EmbedHTML     sql.Null[string]
	EmbedVideoURL sql.Null[string]
	CreatedAt     time.Time
	DeletedAt     sql.Null[time.Time]
}

type CreateParams struct {
	Title         sql.Null[string]
	Description   sql.Null[string]
	LinkURL       sql.Null[string]
	ItemType      string
	EmbedProvider sql.Null[string]
	EmbedVideoID  sql.Null[string]
	EmbedHTML     sql.Null[string]
	EmbedVideoURL sql.Null[string]
}

// QCreate creates a new item.
func QCreate(ctx context.Context, db *sql.DB, p CreateParams) (Row, error) {
	query := `
		INSERT INTO item (title, description, link_url, item_type, embed_provider, embed_video_id, embed_html, embed_video_url)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING id, pub_id, title, description, link_url, item_type, embed_provider, embed_video_id, embed_html, embed_video_url, created_at, deleted_at
	`

	var row Row
	var pubID pgtype.UUID
	err := db.QueryRowContext(ctx, query,
		p.Title, p.Description, p.LinkURL, p.ItemType, p.EmbedProvider, p.EmbedVideoID, p.EmbedHTML, p.EmbedVideoURL,
	).Scan(
		&row.ID, &pubID, &row.Title, &row.Description, &row.LinkURL,
		&row.ItemType, &row.EmbedProvider, &row.EmbedVideoID, &row.EmbedHTML, &row.EmbedVideoURL,
		&row.CreatedAt, &row.DeletedAt,
	)
	if err == nil {
		row.PubID = formatUUID(pubID)
	}
	return row, err
}

// QFindByPubID finds an item by its public ID.
func QFindByPubID(ctx context.Context, db *sql.DB, pubID string) (*Row, error) {
	query := `
		SELECT id, pub_id, title, description, link_url, item_type, embed_provider, embed_video_id, embed_html, embed_video_url, created_at, deleted_at
		FROM item
		WHERE pub_id = $1
	`

	var row Row
	var pubUUID pgtype.UUID
	err := db.QueryRowContext(ctx, query, pubID).Scan(
		&row.ID, &pubUUID, &row.Title, &row.Description, &row.LinkURL,
		&row.ItemType, &row.EmbedProvider, &row.EmbedVideoID, &row.EmbedHTML, &row.EmbedVideoURL,
		&row.CreatedAt, &row.DeletedAt,
	)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	row.PubID = formatUUID(pubUUID)
	return &row, nil
}

// QFindByID finds an item by its internal ID.
func QFindByID(ctx context.Context, db *sql.DB, id int64) (*Row, error) {
	query := `
		SELECT id, pub_id, title, description, link_url, item_type, embed_provider, embed_video_id, embed_html, embed_video_url, created_at, deleted_at
		FROM item
		WHERE id = $1
	`

	var row Row
	var pubUUID pgtype.UUID
	err := db.QueryRowContext(ctx, query, id).Scan(
		&row.ID, &pubUUID, &row.Title, &row.Description, &row.LinkURL,
		&row.ItemType, &row.EmbedProvider, &row.EmbedVideoID, &row.EmbedHTML, &row.EmbedVideoURL,
		&row.CreatedAt, &row.DeletedAt,
	)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	row.PubID = formatUUID(pubUUID)
	return &row, nil
}

// QList returns all non-deleted items, newest first.
func QList(ctx context.Context, db *sql.DB) ([]Row, error) {
	query := `
		SELECT id, pub_id, title, description, link_url, item_type, embed_provider, embed_video_id, embed_html, embed_video_url, created_at, deleted_at
		FROM item
		WHERE deleted_at IS NULL
		ORDER BY created_at DESC
	`

	rows, err := db.QueryContext(ctx, query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var items []Row
	for rows.Next() {
		var row Row
		var pubUUID pgtype.UUID
		if err := rows.Scan(
			&row.ID, &pubUUID, &row.Title, &row.Description, &row.LinkURL,
			&row.ItemType, &row.EmbedProvider, &row.EmbedVideoID, &row.EmbedHTML, &row.EmbedVideoURL,
			&row.CreatedAt, &row.DeletedAt,
		); err != nil {
			return nil, err
		}
		row.PubID = formatUUID(pubUUID)
		items = append(items, row)
	}
	return items, rows.Err()
}

// QListByTag returns all non-deleted items with a specific tag, newest first.
func QListByTag(ctx context.Context, db *sql.DB, tagName string) ([]Row, error) {
	query := `
		SELECT i.id, i.pub_id, i.title, i.description, i.link_url, i.item_type, i.embed_provider, i.embed_video_id, i.embed_html, i.embed_video_url, i.created_at, i.deleted_at
		FROM item i
		JOIN item_tag it ON i.id = it.item_id
		JOIN tag t ON it.tag_id = t.id
		WHERE i.deleted_at IS NULL AND t.name = $1
		ORDER BY i.created_at DESC
	`

	rows, err := db.QueryContext(ctx, query, tagName)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var items []Row
	for rows.Next() {
		var row Row
		var pubUUID pgtype.UUID
		if err := rows.Scan(
			&row.ID, &pubUUID, &row.Title, &row.Description, &row.LinkURL,
			&row.ItemType, &row.EmbedProvider, &row.EmbedVideoID, &row.EmbedHTML, &row.EmbedVideoURL,
			&row.CreatedAt, &row.DeletedAt,
		); err != nil {
			return nil, err
		}
		row.PubID = formatUUID(pubUUID)
		items = append(items, row)
	}
	return items, rows.Err()
}

type UpdateParams struct {
	Title       sql.Null[string]
	Description sql.Null[string]
	LinkURL     sql.Null[string]
}

// QUpdate updates an item's editable fields.
func QUpdate(ctx context.Context, db *sql.DB, id int64, p UpdateParams) error {
	query := `
		UPDATE item
		SET title = $2, description = $3, link_url = $4
		WHERE id = $1
	`
	_, err := db.ExecContext(ctx, query, id, p.Title, p.Description, p.LinkURL)
	return err
}

// QUpdateType updates an item's type.
func QUpdateType(ctx context.Context, db *sql.DB, id int64, itemType string) error {
	query := `UPDATE item SET item_type = $2 WHERE id = $1`
	_, err := db.ExecContext(ctx, query, id, itemType)
	return err
}

// QUpdateVideoURL updates an item's embed video URL.
func QUpdateVideoURL(ctx context.Context, db *sql.DB, id int64, videoURL string) error {
	query := `UPDATE item SET embed_video_url = $2 WHERE id = $1`
	_, err := db.ExecContext(ctx, query, id, videoURL)
	return err
}

// QSoftDelete soft deletes an item.
func QSoftDelete(ctx context.Context, db *sql.DB, id int64) error {
	query := `UPDATE item SET deleted_at = NOW() WHERE id = $1`
	_, err := db.ExecContext(ctx, query, id)
	return err
}

// QRestore restores a soft-deleted item.
func QRestore(ctx context.Context, db *sql.DB, id int64) error {
	query := `UPDATE item SET deleted_at = NULL WHERE id = $1`
	_, err := db.ExecContext(ctx, query, id)
	return err
}

func formatUUID(u pgtype.UUID) string {
	if !u.Valid {
		return ""
	}
	b := u.Bytes
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}
