package service

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"log/slog"
	"time"

	"github.com/TopherMayor/unified-media-manager/internal/db"
)

type BlocklistItem struct {
	ID            int64           `json:"id"`
	ReleaseTitle  string          `json:"release_title"`
	SourceTitle   *string         `json:"source_title,omitempty"`
	Quality       json.RawMessage `json:"quality"`
	Indexer       *string         `json:"indexer,omitempty"`
	Protocol      string          `json:"protocol"`
	TorrentHash   *string         `json:"torrent_hash,omitempty"`
	Size          *int64          `json:"size,omitempty"`
	Message       *string         `json:"message,omitempty"`
	MediaID       *int64          `json:"media_id,omitempty"`
	BlockReason   string          `json:"block_reason"`
	AutoExpiresAt *time.Time      `json:"auto_expires_at,omitempty"`
	CreatedAt     time.Time       `json:"created_at"`
}

type BlocklistFilters struct {
	Page     int
	PageSize int
}

type AddBlocklistRequest struct {
	ReleaseTitle  string          `json:"release_title"`
	SourceTitle   *string         `json:"source_title,omitempty"`
	Quality       json.RawMessage `json:"quality,omitempty"`
	Indexer       *string         `json:"indexer,omitempty"`
	Protocol      string          `json:"protocol,omitempty"`
	TorrentHash   *string         `json:"torrent_hash,omitempty"`
	Size          *int64          `json:"size,omitempty"`
	Message       *string         `json:"message,omitempty"`
	MediaID       *int64          `json:"media_id,omitempty"`
	BlockReason   *string         `json:"block_reason,omitempty"`
	AutoExpiresAt *time.Time      `json:"auto_expires_at,omitempty"`
}

const blocklistColumns = `id, release_title, source_title, quality, indexer, protocol,
	torrent_hash, size, message, media_id, block_reason, auto_expires_at, created_at`

type BlocklistService struct {
	db *db.DB
}

func NewBlocklistService(database *db.DB) *BlocklistService {
	return &BlocklistService{db: database}
}

func scanBlocklistItem(scanner interface{ Scan(...interface{}) error }) (*BlocklistItem, error) {
	var item BlocklistItem
	var sourceTitle, indexer, torrentHash, message sql.NullString
	var size, mediaID sql.NullInt64
	var autoExpiresAt sql.NullTime
	var quality []byte

	err := scanner.Scan(&item.ID, &item.ReleaseTitle, &sourceTitle, &quality, &indexer,
		&item.Protocol, &torrentHash, &size, &message, &mediaID, &item.BlockReason,
		&autoExpiresAt, &item.CreatedAt)
	if err != nil {
		return nil, err
	}

	if sourceTitle.Valid {
		item.SourceTitle = &sourceTitle.String
	}
	if indexer.Valid {
		item.Indexer = &indexer.String
	}
	if torrentHash.Valid {
		item.TorrentHash = &torrentHash.String
	}
	if message.Valid {
		item.Message = &message.String
	}
	if size.Valid {
		item.Size = &size.Int64
	}
	if mediaID.Valid {
		item.MediaID = &mediaID.Int64
	}
	if autoExpiresAt.Valid {
		item.AutoExpiresAt = &autoExpiresAt.Time
	}
	if quality != nil {
		item.Quality = json.RawMessage(quality)
	}
	return &item, nil
}

func (s *BlocklistService) List(ctx context.Context, filters BlocklistFilters) ([]BlocklistItem, int, error) {
	var total int
	if err := s.db.Pool.QueryRow(ctx, "SELECT COUNT(*) FROM blocklist").Scan(&total); err != nil {
		return nil, 0, fmt.Errorf("count blocklist: %w", err)
	}

	rows, err := s.db.Pool.Query(ctx,
		fmt.Sprintf("SELECT %s FROM blocklist ORDER BY created_at DESC LIMIT $1 OFFSET $2", blocklistColumns),
		filters.PageSize, (filters.Page-1)*filters.PageSize)
	if err != nil {
		return nil, 0, fmt.Errorf("list blocklist: %w", err)
	}
	defer rows.Close()

	var items []BlocklistItem
	for rows.Next() {
		item, err := scanBlocklistItem(rows)
		if err != nil {
			slog.Error("failed to scan blocklist item", "error", err)
			continue
		}
		items = append(items, *item)
	}

	return items, total, nil
}

func (s *BlocklistService) Add(ctx context.Context, req AddBlocklistRequest) (int64, error) {
	protocol := req.Protocol
	if protocol == "" {
		protocol = "torrent"
	}
	blockReason := "manual"
	if req.BlockReason != nil {
		blockReason = *req.BlockReason
	}
	quality := req.Quality
	if quality == nil {
		quality = json.RawMessage("{}")
	}

	var id int64
	err := s.db.Pool.QueryRow(ctx,
		`INSERT INTO blocklist (release_title, source_title, quality, indexer, protocol,
		 torrent_hash, size, message, media_id, block_reason, auto_expires_at)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`,
		req.ReleaseTitle, req.SourceTitle, quality, req.Indexer, protocol,
		req.TorrentHash, req.Size, req.Message, req.MediaID, blockReason, req.AutoExpiresAt).Scan(&id)
	if err != nil {
		return 0, fmt.Errorf("create blocklist entry: %w", err)
	}

	return id, nil
}

func (s *BlocklistService) Delete(ctx context.Context, id int64) error {
	tag, err := s.db.Pool.Exec(ctx, "DELETE FROM blocklist WHERE id = $1", id)
	if err != nil {
		return fmt.Errorf("delete blocklist item: %w", err)
	}
	if tag.RowsAffected() == 0 {
		return fmt.Errorf("blocklist item not found")
	}
	return nil
}

func (s *BlocklistService) Clear(ctx context.Context) (int64, error) {
	tag, err := s.db.Pool.Exec(ctx, "DELETE FROM blocklist")
	if err != nil {
		return 0, fmt.Errorf("clear blocklist: %w", err)
	}
	return tag.RowsAffected(), nil
}

func (s *BlocklistService) ClearExpired(ctx context.Context) (int64, error) {
	tag, err := s.db.Pool.Exec(ctx,
		"DELETE FROM blocklist WHERE auto_expires_at IS NOT NULL AND auto_expires_at < NOW()")
	if err != nil {
		return 0, fmt.Errorf("clear expired blocklist: %w", err)
	}
	return tag.RowsAffected(), nil
}
