package repo

import (
	"context"
	"errors"
	"fmt"
	"time"

	ubent "github.com/tidbcloud/devops/entity/userbehavior"
	ubmod "github.com/tidbcloud/devops/models/userbehavior"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

const (
	GTMSentEmailStatusQueued    = "queued"
	GTMSentEmailStatusSending   = "sending"
	GTMSentEmailStatusSent      = "sent"
	GTMSentEmailStatusFailed    = "failed"
	GTMSentEmailStatusCancelled = "cancelled"
)

type GTMSentEmailDriver interface {
	Create(ctx context.Context, email *ubent.GTMSentEmail) (*ubent.GTMSentEmail, error)
	GetByID(ctx context.Context, id uint64) (*ubent.GTMSentEmail, error)
	GetByLeadID(ctx context.Context, leadID uint64) ([]*ubent.GTMSentEmail, error)
	GetByMessageID(ctx context.Context, messageID string) (*ubent.GTMSentEmail, error)
	Update(ctx context.Context, email *ubent.GTMSentEmail) (*ubent.GTMSentEmail, error)

	// Queued-delivery operations
	ListDue(ctx context.Context, now time.Time, limit int) ([]*ubent.GTMSentEmail, error)
	MarkSending(ctx context.Context, id uint64) (bool, error)
	MarkSent(ctx context.Context, id uint64, sentAt time.Time, messageID string) error
	MarkFailed(ctx context.Context, id uint64, attemptedAt time.Time, errMsg string) error
	CancelQueuedByTenant(ctx context.Context, tenantID uint64) (int64, error)
	HasSendingOrSentByTenant(ctx context.Context, tenantID uint64) (bool, error)
}

type gtmSentEmailDriver driver

func (d *gtmSentEmailDriver) Create(ctx context.Context, emailent *ubent.GTMSentEmail) (*ubent.GTMSentEmail, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	emailmod := d.cvter.EntityToModel.GTMSentEmail(emailent)

	if err := wdb.Model(&ubmod.GTMSentEmail{}).Create(emailmod).Error; err != nil {
		return nil, fmt.Errorf("create GTM sent email failed: %w", err)
	}

	return d.cvter.ModelToEntity.GTMSentEmail(emailmod), nil
}

func (d *gtmSentEmailDriver) GetByID(ctx context.Context, id uint64) (*ubent.GTMSentEmail, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	var emailmod ubmod.GTMSentEmail

	if err := rdb.Model(&ubmod.GTMSentEmail{}).Where("id = ?", id).First(&emailmod).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, fmt.Errorf("get GTM sent email by id %d failed: %w", id, err)
	}

	return d.cvter.ModelToEntity.GTMSentEmail(&emailmod), nil
}

func (d *gtmSentEmailDriver) GetByLeadID(ctx context.Context, leadID uint64) ([]*ubent.GTMSentEmail, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	var emailmods []ubmod.GTMSentEmail

	if err := rdb.Model(&ubmod.GTMSentEmail{}).Where("lead_id = ?", leadID).Find(&emailmods).Error; err != nil {
		return nil, fmt.Errorf("get GTM sent emails by lead_id %d failed: %w", leadID, err)
	}

	return d.cvter.ModelToEntity.GTMSentEmails(emailmods), nil
}

func (d *gtmSentEmailDriver) GetByMessageID(ctx context.Context, messageID string) (*ubent.GTMSentEmail, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	var emailmod ubmod.GTMSentEmail

	if err := rdb.Model(&ubmod.GTMSentEmail{}).Where("message_id = ?", messageID).First(&emailmod).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, fmt.Errorf("get GTM sent email by message_id %s failed: %w", messageID, err)
	}

	return d.cvter.ModelToEntity.GTMSentEmail(&emailmod), nil
}

func (d *gtmSentEmailDriver) Update(ctx context.Context, emailent *ubent.GTMSentEmail) (*ubent.GTMSentEmail, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	emailmod := d.cvter.EntityToModel.GTMSentEmail(emailent)

	if err := wdb.Model(&ubmod.GTMSentEmail{}).Where("id = ?", emailmod.ID).Updates(emailmod).Error; err != nil {
		return nil, fmt.Errorf("update GTM sent email %d failed: %w", emailmod.ID, err)
	}

	// Re-fetch to get updated values
	var updated ubmod.GTMSentEmail
	if err := wdb.Model(&ubmod.GTMSentEmail{}).Where("id = ?", emailmod.ID).First(&updated).Error; err != nil {
		return nil, fmt.Errorf("fetch updated GTM sent email %d failed: %w", emailmod.ID, err)
	}

	return d.cvter.ModelToEntity.GTMSentEmail(&updated), nil
}

func (d *gtmSentEmailDriver) ListDue(ctx context.Context, now time.Time, limit int) ([]*ubent.GTMSentEmail, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	var emailmods []ubmod.GTMSentEmail

	q := rdb.Model(&ubmod.GTMSentEmail{}).
		Where("status = ? AND scheduled_at <= ?", GTMSentEmailStatusQueued, now).
		Order("scheduled_at ASC")
	if limit > 0 {
		q = q.Limit(limit)
	}

	if err := q.Find(&emailmods).Error; err != nil {
		return nil, fmt.Errorf("list due GTM queued emails failed: %w", err)
	}

	return d.cvter.ModelToEntity.GTMSentEmails(emailmods), nil
}

func (d *gtmSentEmailDriver) MarkSending(ctx context.Context, id uint64) (bool, error) {
	wdb := d.dbProvider.WriteDB(ctx)

	res := wdb.Model(&ubmod.GTMSentEmail{}).
		Where("id = ? AND status = ?", id, GTMSentEmailStatusQueued).
		Update("status", GTMSentEmailStatusSending)
	if res.Error != nil {
		return false, fmt.Errorf("mark GTM queued email %d as sending failed: %w", id, res.Error)
	}

	return res.RowsAffected == 1, nil
}

func (d *gtmSentEmailDriver) MarkSent(ctx context.Context, id uint64, sentAt time.Time, messageID string) error {
	wdb := d.dbProvider.WriteDB(ctx)

	res := wdb.Model(&ubmod.GTMSentEmail{}).
		Where("id = ? AND status = ?", id, GTMSentEmailStatusSending).
		Updates(map[string]any{
			"status":          GTMSentEmailStatusSent,
			"sent_at":         sentAt,
			"message_id":      messageID,
			"error_message":   nil,
			"attempt_count":   gorm.Expr("attempt_count + 1"),
			"last_attempt_at": sentAt,
		})
	if res.Error != nil {
		return fmt.Errorf("mark GTM sending email %d as sent failed: %w", id, res.Error)
	}
	if res.RowsAffected != 1 {
		return fmt.Errorf("mark GTM sending email %d as sent failed: no rows affected", id)
	}
	return nil
}

func (d *gtmSentEmailDriver) MarkFailed(ctx context.Context, id uint64, attemptedAt time.Time, errMsg string) error {
	wdb := d.dbProvider.WriteDB(ctx)

	res := wdb.Model(&ubmod.GTMSentEmail{}).
		Where("id = ? AND status = ?", id, GTMSentEmailStatusSending).
		Updates(map[string]any{
			"status":          GTMSentEmailStatusFailed,
			"error_message":   errMsg,
			"attempt_count":   gorm.Expr("attempt_count + 1"),
			"last_attempt_at": attemptedAt,
		})
	if res.Error != nil {
		return fmt.Errorf("mark GTM sending email %d as failed failed: %w", id, res.Error)
	}
	if res.RowsAffected != 1 {
		return fmt.Errorf("mark GTM sending email %d as failed failed: no rows affected", id)
	}
	return nil
}

func (d *gtmSentEmailDriver) CancelQueuedByTenant(ctx context.Context, tenantID uint64) (int64, error) {
	wdb := d.dbProvider.WriteDB(ctx)

	res := wdb.Model(&ubmod.GTMSentEmail{}).
		Where("tenant_id = ? AND status = ?", tenantID, GTMSentEmailStatusQueued).
		Update("status", GTMSentEmailStatusCancelled)
	if res.Error != nil {
		return 0, fmt.Errorf("cancel GTM queued emails for tenant %d failed: %w", tenantID, res.Error)
	}
	return res.RowsAffected, nil
}

func (d *gtmSentEmailDriver) HasSendingOrSentByTenant(ctx context.Context, tenantID uint64) (bool, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	var count int64

	if err := rdb.Model(&ubmod.GTMSentEmail{}).
		Where("tenant_id = ? AND status IN ?", tenantID, []string{GTMSentEmailStatusSending, GTMSentEmailStatusSent}).
		Count(&count).Error; err != nil {
		return false, fmt.Errorf("check GTM sending/sent emails for tenant %d failed: %w", tenantID, err)
	}

	return count > 0, nil
}

type GTMEmailSuppressionDriver interface {
	Upsert(ctx context.Context, suppression *ubent.GTMEmailSuppression) (*ubent.GTMEmailSuppression, error)
	Exists(ctx context.Context, tenantID uint64, campaignName string) (bool, error)
}

type gtmEmailSuppressionDriver driver

func (d *gtmEmailSuppressionDriver) Upsert(ctx context.Context, suppressionent *ubent.GTMEmailSuppression) (*ubent.GTMEmailSuppression, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	now := time.Now().UTC()
	suppressionmod := d.cvter.EntityToModel.GTMEmailSuppression(suppressionent)
	suppressionmod.UpdatedAt = &now

	if err := wdb.Model(&ubmod.GTMEmailSuppression{}).
		Clauses(clause.OnConflict{
			Columns: []clause.Column{{Name: "tenant_id"}, {Name: "campaign_name"}},
			DoUpdates: clause.Assignments(map[string]any{
				"source":     suppressionmod.Source,
				"reason":     suppressionmod.Reason,
				"updated_at": now,
			}),
		}).
		Create(suppressionmod).Error; err != nil {
		return nil, fmt.Errorf("upsert GTM email suppression failed: %w", err)
	}

	var persisted ubmod.GTMEmailSuppression
	if err := wdb.Model(&ubmod.GTMEmailSuppression{}).
		Where("tenant_id = ? AND campaign_name = ?", suppressionmod.TenantID, suppressionmod.CampaignName).
		First(&persisted).Error; err != nil {
		return nil, fmt.Errorf("fetch GTM email suppression failed: %w", err)
	}

	return d.cvter.ModelToEntity.GTMEmailSuppression(&persisted), nil
}

func (d *gtmEmailSuppressionDriver) Exists(ctx context.Context, tenantID uint64, campaignName string) (bool, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	var count int64

	if err := rdb.Model(&ubmod.GTMEmailSuppression{}).
		Where("tenant_id = ? AND campaign_name = ?", tenantID, campaignName).
		Count(&count).Error; err != nil {
		return false, fmt.Errorf("check GTM email suppression for tenant %d campaign %s failed: %w", tenantID, campaignName, err)
	}

	return count > 0, nil
}
