package repository

import (
	"context"
	"encoding/json"
	"strconv"

	dbgen "Yimaru-Backend/gen/db"
	"Yimaru-Backend/internal/domain"
	"Yimaru-Backend/internal/ports"

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

func NewNotificationStore(s *Store) ports.NotificationStore {
	return s
}

/* =========================
   Create
   ========================= */

func (r *Store) CreateNotification(
	ctx context.Context,
	n *domain.Notification,
) (*domain.Notification, error) {

	receiverType := string(n.ReceiverType)
	if receiverType == "" {
		receiverType = string(domain.ReceiverTypeUser)
	}

	params := dbgen.CreateNotificationParams{
		UserID:       n.RecipientID,
		ReceiverType: receiverType,
		Type:         string(n.Type),
		Level:        string(n.Level),
		Channel:      pgtype.Text{String: string(n.DeliveryChannel), Valid: true},
		Title:        n.Payload.Headline,
		Message:      n.Payload.Message,
		Payload:      marshalPayload(n.Payload),
	}

	dbNotif, err := r.queries.CreateNotification(ctx, params)
	if err != nil {
		return nil, err
	}

	return mapDBToDomain(&dbNotif), nil
}

/* =========================
   Read
   ========================= */

func (r *Store) GetUserNotifications(
	ctx context.Context,
	userID int64,
	limit, offset int,
) ([]domain.Notification, int64, error) {

	params := dbgen.GetUserNotificationsParams{
		UserID: userID,
		Limit:  int32(limit),
		Offset: int32(offset),
	}

	rows, err := r.queries.GetUserNotifications(ctx, params)
	if err != nil {
		return nil, 0, err
	}

	total, err := r.queries.GetUserNotificationCount(ctx, userID)
	if err != nil {
		return nil, 0, err
	}

	result := make([]domain.Notification, 0, len(rows))
	for _, row := range rows {
		result = append(result, *mapDBToDomain(&row))
	}

	return result, total, nil
}

func (r *Store) GetAllNotifications(
	ctx context.Context,
	limit, offset int,
) ([]domain.Notification, error) {

	rows, err := r.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{
		Limit:  int32(limit),
		Offset: int32(offset),
	})
	if err != nil {
		return nil, err
	}

	result := make([]domain.Notification, 0, len(rows))
	for _, row := range rows {
		result = append(result, *mapDBToDomain(&row))
	}

	return result, nil
}

func (r *Store) CountUnreadNotifications(
	ctx context.Context,
	userID int64,
) (int64, error) {
	return r.queries.CountUnreadNotifications(ctx, userID)
}

/* =========================
   Update
   ========================= */

func (r *Store) MarkNotificationAsRead(
	ctx context.Context,
	id int64,
) (*domain.Notification, error) {

	dbNotif, err := r.queries.MarkNotificationAsRead(ctx, id)
	if err != nil {
		return nil, err
	}

	return mapDBToDomain(&dbNotif), nil
}

func (r *Store) MarkAllUserNotificationsAsRead(
	ctx context.Context,
	userID int64,
) error {
	return r.queries.MarkAllUserNotificationsAsRead(ctx, userID)
}

func (r *Store) MarkNotificationAsUnread(
	ctx context.Context,
	id int64,
) (*domain.Notification, error) {

	dbNotif, err := r.queries.MarkNotificationAsUnread(ctx, id)
	if err != nil {
		return nil, err
	}

	return mapDBToDomain(&dbNotif), nil
}

func (r *Store) MarkAllUserNotificationsAsUnread(
	ctx context.Context,
	userID int64,
) error {
	return r.queries.MarkAllUserNotificationsAsUnread(ctx, userID)
}

/* =========================
   Delete
   ========================= */

func (r *Store) DeleteUserNotifications(
	ctx context.Context,
	userID int64,
) error {
	return r.queries.DeleteUserNotifications(ctx, userID)
}

/* =========================
   Mapping
   ========================= */

func mapDBToDomain(db *dbgen.Notification) *domain.Notification {
	payload, err := unmarshalPayload(db.Payload)
	if err != nil {
		payload = domain.NotificationPayload{}
	}

	var channel domain.DeliveryChannel
	if db.Channel.Valid {
		channel = domain.DeliveryChannel(db.Channel.String)
	}

	return &domain.Notification{
		ID:              strconv.FormatInt(db.ID, 10),
		RecipientID:     db.UserID,
		ReceiverType:    domain.ReceiverType(db.ReceiverType),
		Type:            domain.NotificationType(db.Type),
		Level:           domain.NotificationLevel(db.Level),
		DeliveryChannel: channel,
		DeliveryStatus:  domain.DeliveryStatusPending,
		Payload: domain.NotificationPayload{
			Headline: payload.Headline,
			Message:  payload.Message,
		},
		IsRead:    db.IsRead,
		Timestamp: db.CreatedAt.Time,
		// ReadAt:    db.ReadAt.Time,
	}
}

/* =========================
   JSON Helpers
   ========================= */

func marshalPayload(p domain.NotificationPayload) []byte {
	b, _ := json.Marshal(p)
	return b
}

func unmarshalPayload(b []byte) (domain.NotificationPayload, error) {
	var p domain.NotificationPayload
	if len(b) == 0 {
		return p, nil
	}
	if err := json.Unmarshal(b, &p); err != nil {
		return p, err
	}
	return p, nil
}
