package postgres

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"time"

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

	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/domain"
	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/port"
)

// Compile-time interface check.
var _ port.PersonaRepository = (*PersonaRepository)(nil)

// personaRow maps to the personas table.
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"`
	CreatedAt   time.Time      `db:"created_at"`
}

func (r *personaRow) toDomain() *domain.Persona {
	p := &domain.Persona{
		ID:          domain.PersonaID(r.ID),
		Name:        r.Name,
		Handle:      r.Handle,
		Gender:      r.Gender,
		Description: r.Description,
		Tags:        []string(r.Tags),
		Status:      domain.PersonaStatus(r.Status),
		CreatedAt:   r.CreatedAt,
		ImageURLs:   []string(r.ImageURLs),
		VideoURLs:   []string(r.VideoURLs),
	}
	if p.Tags == nil {
		p.Tags = []string{}
	}
	if p.ImageURLs == nil {
		p.ImageURLs = []string{}
	}
	if p.VideoURLs == nil {
		p.VideoURLs = []string{}
	}
	if len(r.SpecJSON) > 0 {
		p.SpecJSON = json.RawMessage(r.SpecJSON)
	}
	if r.AnchorURL != nil {
		p.AnchorURL = *r.AnchorURL
	}
	if r.AvatarURL != nil {
		p.AvatarURL = *r.AvatarURL
	}
	if r.BannerURL != nil {
		p.BannerURL = *r.BannerURL
	}
	return p
}

// PersonaRepository implements port.PersonaRepository with PostgreSQL/CockroachDB.
type PersonaRepository struct {
	db *sqlx.DB
}

// NewPersonaRepository creates a new Postgres-backed persona repository.
func NewPersonaRepository(db *sqlx.DB) *PersonaRepository {
	return &PersonaRepository{db: db}
}

func (r *PersonaRepository) Create(ctx context.Context, persona *domain.Persona) error {
	var specJSON []byte
	if persona.SpecJSON != nil {
		specJSON = []byte(persona.SpecJSON)
	}

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

	err := r.db.QueryRowContext(ctx, `
		INSERT INTO personas (name, handle, gender, description, tags, spec_json, anchor_url, avatar_url, banner_url, image_urls, video_urls, status, created_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
		RETURNING id
	`,
		persona.Name,
		persona.Handle,
		persona.Gender,
		persona.Description,
		pq.StringArray(persona.Tags),
		specJSON,
		anchorURL,
		avatarURL,
		bannerURL,
		pq.StringArray(persona.ImageURLs),
		pq.StringArray(persona.VideoURLs),
		string(persona.Status),
		persona.CreatedAt,
	).Scan(&persona.ID)
	if err != nil {
		if isUniqueViolation(err) {
			return domain.ErrDuplicateHandle
		}
		return fmt.Errorf("insert persona: %w", err)
	}
	return nil
}

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

func (r *PersonaRepository) List(ctx context.Context, limit, offset int) ([]*domain.Persona, error) {
	var rows []personaRow
	err := r.db.SelectContext(ctx, &rows, `
		SELECT id, name, handle, gender, description, tags, spec_json, anchor_url, avatar_url, banner_url, image_urls, video_urls, status, created_at
		FROM personas
		ORDER BY created_at DESC
		LIMIT $1 OFFSET $2
	`, limit, offset)
	if err != nil {
		return nil, fmt.Errorf("list personas: %w", err)
	}

	result := make([]*domain.Persona, len(rows))
	for i := range rows {
		result[i] = rows[i].toDomain()
	}
	return result, nil
}

func (r *PersonaRepository) Update(ctx context.Context, persona *domain.Persona) error {
	var specJSON []byte
	if persona.SpecJSON != nil {
		specJSON = []byte(persona.SpecJSON)
	}

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

	result, err := r.db.ExecContext(ctx, `
		UPDATE personas
		SET name = $2, handle = $3, gender = $4, description = $5, tags = $6,
		    spec_json = $7, anchor_url = $8, avatar_url = $9, banner_url = $10,
		    image_urls = $11, video_urls = $12, status = $13
		WHERE id = $1
	`,
		string(persona.ID),
		persona.Name,
		persona.Handle,
		persona.Gender,
		persona.Description,
		pq.StringArray(persona.Tags),
		specJSON,
		anchorURL,
		avatarURL,
		bannerURL,
		pq.StringArray(persona.ImageURLs),
		pq.StringArray(persona.VideoURLs),
		string(persona.Status),
	)
	if err != nil {
		if isUniqueViolation(err) {
			return domain.ErrDuplicateHandle
		}
		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 domain.ErrPersonaNotFound
	}
	return nil
}
