package repository

import (
	"context"
	"time"

	"github.com/Wei-Shaw/sub2api/internal/service"

	"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"

	"gorm.io/gorm"
)

type proxyRepository struct {
	db *gorm.DB
}

func NewProxyRepository(db *gorm.DB) service.ProxyRepository {
	return &proxyRepository{db: db}
}

func (r *proxyRepository) Create(ctx context.Context, proxy *service.Proxy) error {
	m := proxyModelFromService(proxy)
	err := r.db.WithContext(ctx).Create(m).Error
	if err == nil {
		applyProxyModelToService(proxy, m)
	}
	return err
}

func (r *proxyRepository) GetByID(ctx context.Context, id int64) (*service.Proxy, error) {
	var m proxyModel
	err := r.db.WithContext(ctx).First(&m, id).Error
	if err != nil {
		return nil, translatePersistenceError(err, service.ErrProxyNotFound, nil)
	}
	return proxyModelToService(&m), nil
}

func (r *proxyRepository) Update(ctx context.Context, proxy *service.Proxy) error {
	m := proxyModelFromService(proxy)
	err := r.db.WithContext(ctx).Save(m).Error
	if err == nil {
		applyProxyModelToService(proxy, m)
	}
	return err
}

func (r *proxyRepository) Delete(ctx context.Context, id int64) error {
	return r.db.WithContext(ctx).Delete(&proxyModel{}, id).Error
}

func (r *proxyRepository) List(ctx context.Context, params pagination.PaginationParams) ([]service.Proxy, *pagination.PaginationResult, error) {
	return r.ListWithFilters(ctx, params, "", "", "")
}

// ListWithFilters lists proxies with optional filtering by protocol, status, and search query
func (r *proxyRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, protocol, status, search string) ([]service.Proxy, *pagination.PaginationResult, error) {
	var proxies []proxyModel
	var total int64

	db := r.db.WithContext(ctx).Model(&proxyModel{})

	// Apply filters
	if protocol != "" {
		db = db.Where("protocol = ?", protocol)
	}
	if status != "" {
		db = db.Where("status = ?", status)
	}
	if search != "" {
		searchPattern := "%" + search + "%"
		db = db.Where("name ILIKE ?", searchPattern)
	}

	if err := db.Count(&total).Error; err != nil {
		return nil, nil, err
	}

	if err := db.Offset(params.Offset()).Limit(params.Limit()).Order("id DESC").Find(&proxies).Error; err != nil {
		return nil, nil, err
	}

	outProxies := make([]service.Proxy, 0, len(proxies))
	for i := range proxies {
		outProxies = append(outProxies, *proxyModelToService(&proxies[i]))
	}

	return outProxies, paginationResultFromTotal(total, params), nil
}

func (r *proxyRepository) ListActive(ctx context.Context) ([]service.Proxy, error) {
	var proxies []proxyModel
	err := r.db.WithContext(ctx).Where("status = ?", service.StatusActive).Find(&proxies).Error
	if err != nil {
		return nil, err
	}
	outProxies := make([]service.Proxy, 0, len(proxies))
	for i := range proxies {
		outProxies = append(outProxies, *proxyModelToService(&proxies[i]))
	}
	return outProxies, nil
}

// ExistsByHostPortAuth checks if a proxy with the same host, port, username, and password exists
func (r *proxyRepository) ExistsByHostPortAuth(ctx context.Context, host string, port int, username, password string) (bool, error) {
	var count int64
	err := r.db.WithContext(ctx).Model(&proxyModel{}).
		Where("host = ? AND port = ? AND username = ? AND password = ?", host, port, username, password).
		Count(&count).Error
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

// CountAccountsByProxyID returns the number of accounts using a specific proxy
func (r *proxyRepository) CountAccountsByProxyID(ctx context.Context, proxyID int64) (int64, error) {
	var count int64
	err := r.db.WithContext(ctx).Table("accounts").
		Where("proxy_id = ?", proxyID).
		Count(&count).Error
	return count, err
}

// GetAccountCountsForProxies returns a map of proxy ID to account count for all proxies
func (r *proxyRepository) GetAccountCountsForProxies(ctx context.Context) (map[int64]int64, error) {
	type result struct {
		ProxyID int64 `gorm:"column:proxy_id"`
		Count   int64 `gorm:"column:count"`
	}
	var results []result
	err := r.db.WithContext(ctx).
		Table("accounts").
		Select("proxy_id, COUNT(*) as count").
		Where("proxy_id IS NOT NULL").
		Group("proxy_id").
		Scan(&results).Error
	if err != nil {
		return nil, err
	}

	counts := make(map[int64]int64)
	for _, r := range results {
		counts[r.ProxyID] = r.Count
	}
	return counts, nil
}

// ListActiveWithAccountCount returns all active proxies with account count, sorted by creation time descending
func (r *proxyRepository) ListActiveWithAccountCount(ctx context.Context) ([]service.ProxyWithAccountCount, error) {
	var proxies []proxyModel
	err := r.db.WithContext(ctx).
		Where("status = ?", service.StatusActive).
		Order("created_at DESC").
		Find(&proxies).Error
	if err != nil {
		return nil, err
	}

	// Get account counts
	counts, err := r.GetAccountCountsForProxies(ctx)
	if err != nil {
		return nil, err
	}

	// Build result with account counts
	result := make([]service.ProxyWithAccountCount, 0, len(proxies))
	for i := range proxies {
		proxy := proxyModelToService(&proxies[i])
		if proxy == nil {
			continue
		}
		result = append(result, service.ProxyWithAccountCount{
			Proxy:        *proxy,
			AccountCount: counts[proxy.ID],
		})
	}

	return result, nil
}

type proxyModel struct {
	ID        int64          `gorm:"primaryKey"`
	Name      string         `gorm:"size:100;not null"`
	Protocol  string         `gorm:"size:20;not null"`
	Host      string         `gorm:"size:255;not null"`
	Port      int            `gorm:"not null"`
	Username  string         `gorm:"size:100"`
	Password  string         `gorm:"size:100"`
	Status    string         `gorm:"size:20;default:active;not null"`
	CreatedAt time.Time      `gorm:"not null"`
	UpdatedAt time.Time      `gorm:"not null"`
	DeletedAt gorm.DeletedAt `gorm:"index"`
}

func (proxyModel) TableName() string { return "proxies" }

func proxyModelToService(m *proxyModel) *service.Proxy {
	if m == nil {
		return nil
	}
	return &service.Proxy{
		ID:        m.ID,
		Name:      m.Name,
		Protocol:  m.Protocol,
		Host:      m.Host,
		Port:      m.Port,
		Username:  m.Username,
		Password:  m.Password,
		Status:    m.Status,
		CreatedAt: m.CreatedAt,
		UpdatedAt: m.UpdatedAt,
	}
}

func proxyModelFromService(p *service.Proxy) *proxyModel {
	if p == nil {
		return nil
	}
	return &proxyModel{
		ID:        p.ID,
		Name:      p.Name,
		Protocol:  p.Protocol,
		Host:      p.Host,
		Port:      p.Port,
		Username:  p.Username,
		Password:  p.Password,
		Status:    p.Status,
		CreatedAt: p.CreatedAt,
		UpdatedAt: p.UpdatedAt,
	}
}

func applyProxyModelToService(proxy *service.Proxy, m *proxyModel) {
	if proxy == nil || m == nil {
		return
	}
	proxy.ID = m.ID
	proxy.CreatedAt = m.CreatedAt
	proxy.UpdatedAt = m.UpdatedAt
}
