// Copyright 2025 eat-pray-ai & OpenWaygate
// SPDX-License-Identifier: Apache-2.0

package subscription

import (
	"errors"
	"fmt"
	"io"

	"github.com/eat-pray-ai/yutu/pkg/common"
	"github.com/jedib0t/go-pretty/v6/table"
	"google.golang.org/api/youtube/v3"
)

var (
	errGetSubscription    = errors.New("failed to get subscription")
	errDeleteSubscription = errors.New("failed to delete subscription")
	errInsertSubscription = errors.New("failed to insert subscription")
)

type Subscription struct {
	common.Fields
	SubscriberChannelId string `yaml:"subscriber_channel_id" json:"subscriber_channel_id,omitempty"`
	Description         string `yaml:"description" json:"description,omitempty"`
	ForChannelId        string `yaml:"for_channel_id" json:"for_channel_id,omitempty"`
	Mine                *bool  `yaml:"mine" json:"mine,omitempty"`
	MyRecentSubscribers *bool  `yaml:"my_recent_subscribers" json:"my_recent_subscribers,omitempty"`
	MySubscribers       *bool  `yaml:"my_subscribers" json:"my_subscribers,omitempty"`
	Order               string `yaml:"order" json:"order,omitempty"`
	Title               string `yaml:"title" json:"title,omitempty"`

	OnBehalfOfContentOwnerChannel string `yaml:"on_behalf_of_content_owner_channel" json:"on_behalf_of_content_owner_channel,omitempty"`
}

type ISubscription[T any] interface {
	Get() ([]*T, error)
	List(io.Writer) error
	Insert(io.Writer) error
	Delete(io.Writer) error
}

type Option func(*Subscription)

func NewSubscription(opts ...Option) ISubscription[youtube.Subscription] {
	s := &Subscription{Fields: common.Fields{}}
	for _, opt := range opts {
		opt(s)
	}
	return s
}

func (s *Subscription) Get() ([]*youtube.Subscription, error) {
	if err := s.EnsureService(); err != nil {
		return nil, err
	}
	call := s.Service.Subscriptions.List(s.Parts)
	if len(s.Ids) > 0 {
		call = call.Id(s.Ids...)
	}
	if s.ChannelId != "" {
		call = call.ChannelId(s.ChannelId)
	}
	if s.ForChannelId != "" {
		call = call.ForChannelId(s.ForChannelId)
	}
	if s.Mine != nil {
		call = call.Mine(*s.Mine)
	}
	if s.MyRecentSubscribers != nil {
		call = call.MyRecentSubscribers(*s.MyRecentSubscribers)
	}
	if s.MySubscribers != nil {
		call = call.MySubscribers(*s.MySubscribers)
	}
	if s.OnBehalfOfContentOwner != "" {
		call = call.OnBehalfOfContentOwner(s.OnBehalfOfContentOwner)
	}
	if s.OnBehalfOfContentOwnerChannel != "" {
		call = call.OnBehalfOfContentOwnerChannel(s.OnBehalfOfContentOwnerChannel)
	}
	if s.Order != "" {
		call = call.Order(s.Order)
	}

	return common.Paginate(
		&s.Fields, call,
		func(r *youtube.SubscriptionListResponse) ([]*youtube.Subscription, string) {
			return r.Items, r.NextPageToken
		}, errGetSubscription,
	)
}

func (s *Subscription) List(writer io.Writer) error {
	subscriptions, err := s.Get()
	if err != nil && subscriptions == nil {
		return err
	}

	common.PrintList(
		s.Output, subscriptions, writer,
		table.Row{"ID", "Kind", "Resource ID", "Channel Title"},
		func(sub *youtube.Subscription) table.Row {
			var resourceId string
			switch sub.Snippet.ResourceId.Kind {
			case "youtube#video":
				resourceId = sub.Snippet.ResourceId.VideoId
			case "youtube#channel":
				resourceId = sub.Snippet.ResourceId.ChannelId
			case "youtube#playlist":
				resourceId = sub.Snippet.ResourceId.PlaylistId
			}
			return table.Row{sub.Id, sub.Snippet.ResourceId.Kind, resourceId, sub.Snippet.Title}
		},
	)
	return err
}

func (s *Subscription) Insert(writer io.Writer) error {
	if err := s.EnsureService(); err != nil {
		return err
	}
	subscription := &youtube.Subscription{
		Snippet: &youtube.SubscriptionSnippet{
			ChannelId:   s.SubscriberChannelId,
			Description: s.Description,
			ResourceId: &youtube.ResourceId{
				ChannelId: s.ChannelId,
			},
			Title: s.Title,
		},
	}

	call := s.Service.Subscriptions.Insert([]string{"snippet"}, subscription)
	res, err := call.Do()
	if err != nil {
		return errors.Join(errInsertSubscription, err)
	}

	common.PrintResult(
		s.Output, res, writer, "Subscription inserted: %s\n", res.Id,
	)
	return nil
}

func (s *Subscription) Delete(writer io.Writer) error {
	if err := s.EnsureService(); err != nil {
		return err
	}
	for _, id := range s.Ids {
		call := s.Service.Subscriptions.Delete(id)
		err := call.Do()
		if err != nil {
			return errors.Join(errDeleteSubscription, err)
		}

		_, _ = fmt.Fprintf(writer, "Subscription %s deleted", id)
	}
	return nil
}

func WithSubscriberChannelId(id string) Option {
	return func(s *Subscription) {
		s.SubscriberChannelId = id
	}
}

func WithDescription(description string) Option {
	return func(s *Subscription) {
		s.Description = description
	}
}

func WithForChannelId(forChannelId string) Option {
	return func(s *Subscription) {
		s.ForChannelId = forChannelId
	}
}

func WithMine(mine *bool) Option {
	return func(s *Subscription) {
		if mine != nil {
			s.Mine = mine
		}
	}
}

func WithMyRecentSubscribers(myRecentSubscribers *bool) Option {
	return func(s *Subscription) {
		if myRecentSubscribers != nil {
			s.MyRecentSubscribers = myRecentSubscribers
		}
	}
}

func WithMySubscribers(mySubscribers *bool) Option {
	return func(s *Subscription) {
		if mySubscribers != nil {
			s.MySubscribers = mySubscribers
		}
	}
}

func WithOnBehalfOfContentOwnerChannel(onBehalfOfContentOwnerChannel string) Option {
	return func(s *Subscription) {
		s.OnBehalfOfContentOwnerChannel = onBehalfOfContentOwnerChannel
	}
}

func WithOrder(order string) Option {
	return func(s *Subscription) {
		s.Order = order
	}
}

func WithTitle(title string) Option {
	return func(s *Subscription) {
		s.Title = title
	}
}

var (
	WithParts      = common.WithParts[*Subscription]
	WithOutput     = common.WithOutput[*Subscription]
	WithService    = common.WithService[*Subscription]
	WithIds        = common.WithIds[*Subscription]
	WithMaxResults = common.WithMaxResults[*Subscription]
	WithChannelId  = common.WithChannelId[*Subscription]

	WithOnBehalfOfContentOwner = common.WithOnBehalfOfContentOwner[*Subscription]
)
