package personagen

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

	"github.com/jmoiron/sqlx"
	"github.com/lib/pq"
)

// ErrPersonaNotFound is returned when a persona ID does not exist.
var ErrPersonaNotFound = errors.New("persona not found")

// personaRow is the database scan target for persona queries.
type personaRow struct {
	ID          string         `db:"id"`
	Name        string         `db:"name"`
	Handle      string         `db:"handle"`
	Gender      string         `db:"gender"`
	Description string         `db:"description"`
	Tags        pq.StringArray `db:"tags"`
	SpecJSON    []byte         `db:"spec_json"`
	AnchorURL   *string        `db:"anchor_url"`
	AvatarURL   *string        `db:"avatar_url"`
	BannerURL   *string        `db:"banner_url"`
	ImageURLs   pq.StringArray `db:"image_urls"`
	VideoURLs   pq.StringArray `db:"video_urls"`
	Status      string         `db:"status"`
}

func (r *personaRow) toRecord() *PersonaRecord {
	rec := &PersonaRecord{
		ID:          r.ID,
		Name:        r.Name,
		Handle:      r.Handle,
		Gender:      r.Gender,
		Description: r.Description,
		Tags:        []string(r.Tags),
		Status:      r.Status,
		ImageURLs:   []string(r.ImageURLs),
		VideoURLs:   []string(r.VideoURLs),
	}
	if rec.Tags == nil {
		rec.Tags = []string{}
	}
	if rec.ImageURLs == nil {
		rec.ImageURLs = []string{}
	}
	if rec.VideoURLs == nil {
		rec.VideoURLs = []string{}
	}
	if len(r.SpecJSON) > 0 {
		rec.SpecJSON = r.SpecJSON
	}
	if r.AnchorURL != nil {
		rec.AnchorURL = *r.AnchorURL
	}
	if r.AvatarURL != nil {
		rec.AvatarURL = *r.AvatarURL
	}
	if r.BannerURL != nil {
		rec.BannerURL = *r.BannerURL
	}
	return rec
}

// PostgresPersonaStore implements PersonaStore using PostgreSQL/CockroachDB.
type PostgresPersonaStore struct {
	db *sqlx.DB
}

// Compile-time interface check.
var _ PersonaStore = (*PostgresPersonaStore)(nil)

// NewPostgresPersonaStore creates a PersonaStore backed by a SQL database.
func NewPostgresPersonaStore(db *sqlx.DB) *PostgresPersonaStore {
	return &PostgresPersonaStore{db: db}
}

func (s *PostgresPersonaStore) GetByID(ctx context.Context, id string) (*PersonaRecord, error) {
	var row personaRow
	err := s.db.QueryRowxContext(ctx, `
		SELECT id, name, handle, gender, description, tags, spec_json,
		       anchor_url, avatar_url, banner_url, image_urls, video_urls, status
		FROM personas WHERE id = $1
	`, id).StructScan(&row)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, ErrPersonaNotFound
		}
		return nil, fmt.Errorf("get persona: %w", err)
	}
	return row.toRecord(), nil
}

func (s *PostgresPersonaStore) Update(ctx context.Context, p *PersonaRecord) error {
	var specJSON []byte
	if p.SpecJSON != nil {
		specJSON = []byte(p.SpecJSON)
	}

	var anchorURL, avatarURL, bannerURL *string
	if p.AnchorURL != "" {
		anchorURL = &p.AnchorURL
	}
	if p.AvatarURL != "" {
		avatarURL = &p.AvatarURL
	}
	if p.BannerURL != "" {
		bannerURL = &p.BannerURL
	}

	result, err := s.db.ExecContext(ctx, `
		UPDATE personas
		SET name = $2, handle = $3, tags = $4, spec_json = $5,
		    anchor_url = $6, avatar_url = $7, banner_url = $8,
		    image_urls = $9, video_urls = $10, status = $11
		WHERE id = $1
	`,
		p.ID,
		p.Name,
		p.Handle,
		pq.StringArray(p.Tags),
		specJSON,
		anchorURL,
		avatarURL,
		bannerURL,
		pq.StringArray(p.ImageURLs),
		pq.StringArray(p.VideoURLs),
		p.Status,
	)
	if err != nil {
		return fmt.Errorf("update persona: %w", err)
	}

	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("update persona rows affected: %w", err)
	}
	if rows == 0 {
		return ErrPersonaNotFound
	}
	return nil
}
