// Copyright (c) 2021 - 2025, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later

package list

import (
	"context"
	stdErr "errors"
	"net/http"
	"time"

	"github.com/autobrr/autobrr/internal/domain"
	"github.com/autobrr/autobrr/internal/download_client"
	"github.com/autobrr/autobrr/internal/filter"
	"github.com/autobrr/autobrr/internal/logger"
	"github.com/autobrr/autobrr/internal/scheduler"

	"github.com/pkg/errors"
	"github.com/rs/zerolog"
)

type Service interface {
	List(ctx context.Context) ([]*domain.List, error)
	FindByID(ctx context.Context, id int64) (*domain.List, error)
	Store(ctx context.Context, list *domain.List) error
	Update(ctx context.Context, list *domain.List) error
	Delete(ctx context.Context, id int64) error
	RefreshAll(ctx context.Context) error
	RefreshList(ctx context.Context, listID int64) error
	RefreshArrLists(ctx context.Context) error
	RefreshOtherLists(ctx context.Context) error
	Start()
}

type service struct {
	log  zerolog.Logger
	repo domain.ListRepo

	httpClient        *http.Client
	scheduler         scheduler.Service
	downloadClientSvc download_client.Service
	filterSvc         filter.Service
}

func NewService(log logger.Logger, repo domain.ListRepo, downloadClientSvc download_client.Service, filterSvc filter.Service, schedulerSvc scheduler.Service) Service {
	return &service{
		log:  log.With().Str("module", "list").Logger(),
		repo: repo,
		httpClient: &http.Client{
			Timeout: 30 * time.Second,
		},
		downloadClientSvc: downloadClientSvc,
		filterSvc:         filterSvc,
		scheduler:         schedulerSvc,
	}
}

func (s *service) List(ctx context.Context) ([]*domain.List, error) {
	data, err := s.repo.List(ctx)
	if err != nil {
		return nil, err
	}

	// attach filters
	for _, list := range data {
		filters, err := s.repo.GetListFilters(ctx, list.ID)
		if err != nil {
			return nil, err
		}

		list.Filters = filters
	}

	return data, nil
}

func (s *service) FindByID(ctx context.Context, id int64) (*domain.List, error) {
	list, err := s.repo.FindByID(ctx, id)
	if err != nil {
		return nil, err
	}

	// attach filters
	filters, err := s.repo.GetListFilters(ctx, list.ID)
	if err != nil {
		return nil, err
	}

	list.Filters = filters

	return list, nil
}

func (s *service) Store(ctx context.Context, list *domain.List) error {
	if err := list.Validate(); err != nil {
		s.log.Error().Err(err).Msgf("could not validate list %s", list.Name)
		return err
	}

	if err := s.repo.Store(ctx, list); err != nil {
		s.log.Error().Err(err).Msgf("could not store list %s", list.Name)
		return err
	}

	s.log.Debug().Msgf("successfully created list %s", list.Name)

	if list.Enabled {
		if err := s.refreshList(ctx, list); err != nil {
			s.log.Error().Err(err).Msgf("could not refresh list %s", list.Name)
			return err
		}
	}

	return nil
}

func (s *service) Update(ctx context.Context, list *domain.List) error {
	if err := list.Validate(); err != nil {
		s.log.Error().Err(err).Msgf("could not validate list %s", list.Name)
		return err
	}

	existingList, err := s.FindByID(ctx, list.ID)
	if err != nil {
		s.log.Error().Err(err).Msgf("could not find list by id: %v", list.ID)
		return err
	}

	if domain.IsRedactedString(list.APIKey) {
		list.APIKey = existingList.APIKey
	}

	if err := s.repo.Update(ctx, list); err != nil {
		s.log.Error().Err(err).Msgf("could not update list %s", list.Name)
		return err
	}

	s.log.Debug().Msgf("successfully updated list %s", list.Name)

	if list.Enabled {
		if err := s.refreshList(ctx, list); err != nil {
			s.log.Error().Err(err).Msgf("could not refresh list %s", list.Name)
			return err
		}
	}

	return nil
}

func (s *service) Delete(ctx context.Context, id int64) error {
	err := s.repo.Delete(ctx, id)
	if err != nil {
		s.log.Error().Err(err).Msgf("could not delete list by id %d", id)
		return err
	}

	s.log.Debug().Msgf("successfully deleted list %d", id)

	return nil
}

func (s *service) RefreshAll(ctx context.Context) error {
	lists, err := s.List(ctx)
	if err != nil {
		return err
	}

	s.log.Debug().Msgf("found %d lists to refresh", len(lists))

	if err := s.refreshAll(ctx, lists); err != nil {
		return err
	}

	s.log.Debug().Msgf("successfully refreshed all lists")

	return nil
}

func (s *service) refreshAll(ctx context.Context, lists []*domain.List) error {
	var processingErrors []error

	for _, listItem := range lists {
		if !listItem.Enabled {
			s.log.Debug().Msgf("list %s is disabled, skipping...", listItem.Name)
			continue
		}

		if err := s.refreshList(ctx, listItem); err != nil {
			if errors.Is(err, domain.ErrRecordNotFound) {
				s.log.Error().Str("type", string(listItem.Type)).Str("list", listItem.Name).Int("client_id", listItem.ClientID).Msgf("client not found for list %s, skipping", listItem.Name)
				continue
			}

			s.log.Error().Err(err).Str("type", string(listItem.Type)).Str("list", listItem.Name).Msgf("error while refreshing %s, continuing with other lists", listItem.Type)

			processingErrors = append(processingErrors, errors.Wrapf(err, "error while refreshing %s", listItem.Name))
		}
	}

	if len(processingErrors) > 0 {
		err := stdErr.Join(processingErrors...)

		s.log.Error().Err(err).Msg("Errors encountered during processing Arrs:")

		return err
	}

	return nil
}

func (s *service) refreshList(ctx context.Context, listItem *domain.List) error {
	s.log.Debug().Msgf("refresh list %s - %s", listItem.Type, listItem.Name)

	var err error

	switch listItem.Type {
	case domain.ListTypeRadarr:
		err = s.radarr(ctx, listItem)

	case domain.ListTypeSonarr:
		err = s.sonarr(ctx, listItem)

	case domain.ListTypeWhisparr:
		err = s.sonarr(ctx, listItem)

	case domain.ListTypeReadarr:
		err = s.readarr(ctx, listItem)

	case domain.ListTypeLidarr:
		err = s.lidarr(ctx, listItem)

	case domain.ListTypeMDBList:
		err = s.mdblist(ctx, listItem)

	case domain.ListTypeMetacritic:
		err = s.metacritic(ctx, listItem)

	case domain.ListTypeSteam:
		err = s.steam(ctx, listItem)

	case domain.ListTypeTrakt:
		err = s.trakt(ctx, listItem)

	case domain.ListTypePlaintext:
		err = s.plaintext(ctx, listItem)

	case domain.ListTypeAniList:
		err = s.anilist(ctx, listItem)

	default:
		err = errors.Errorf("unsupported list type: %s", listItem.Type)
	}

	if err != nil {
		s.log.Error().Err(err).Str("type", string(listItem.Type)).Str("list", listItem.Name).Msgf("error refreshing %s list", listItem.Name)

		// update last run for list and set errs and status
		listItem.LastRefreshStatus = domain.ListRefreshStatusError
		listItem.LastRefreshData = err.Error()
		listItem.LastRefreshTime = time.Now()

		if updateErr := s.repo.UpdateLastRefresh(ctx, listItem); updateErr != nil {
			s.log.Error().Err(updateErr).Str("type", string(listItem.Type)).Str("list", listItem.Name).Msgf("error updating last refresh for %s list", listItem.Name)
			return updateErr
		}

		return err
	}

	listItem.LastRefreshStatus = domain.ListRefreshStatusSuccess
	//listItem.LastRefreshData = err.Error()
	listItem.LastRefreshTime = time.Now()

	if updateErr := s.repo.UpdateLastRefresh(ctx, listItem); updateErr != nil {
		s.log.Error().Err(updateErr).Str("type", string(listItem.Type)).Str("list", listItem.Name).Msgf("error updating last refresh for %s list", listItem.Name)
		return updateErr
	}

	s.log.Debug().Msgf("successfully refreshed list %s", listItem.Name)

	return nil
}

func (s *service) RefreshList(ctx context.Context, listID int64) error {
	list, err := s.FindByID(ctx, listID)
	if err != nil {
		return err
	}

	if err := s.refreshList(ctx, list); err != nil {
		return err
	}

	return nil
}

func (s *service) RefreshArrLists(ctx context.Context) error {
	lists, err := s.List(ctx)
	if err != nil {
		return err
	}

	var selectedLists []*domain.List
	for _, list := range lists {
		if list.ListTypeArr() && list.Enabled {
			selectedLists = append(selectedLists, list)
		}
	}

	if err := s.refreshAll(ctx, selectedLists); err != nil {
		return err
	}

	return nil
}

func (s *service) RefreshOtherLists(ctx context.Context) error {
	lists, err := s.List(ctx)
	if err != nil {
		return err
	}

	var selectedLists []*domain.List
	for _, list := range lists {
		if list.ListTypeList() && list.Enabled {
			selectedLists = append(selectedLists, list)
		}
	}

	if err := s.refreshAll(ctx, selectedLists); err != nil {
		return err
	}

	return nil
}

// scheduleJob start list updater in the background
func (s *service) scheduleJob() error {
	identifierKey := "lists-updater"

	job := NewRefreshListsJob(s.log.With().Str("job", identifierKey).Logger(), s)

	// schedule job to run every 6th hour
	id, err := s.scheduler.AddJob(job, "0 */6 * * *", identifierKey)
	if err != nil {
		return err
	}

	s.log.Debug().Msgf("scheduled job with id %d", id)

	return nil
}

func (s *service) Start() {
	if err := s.scheduleJob(); err != nil {
		s.log.Error().Err(err).Msg("error while scheduling job")
	}
}
