using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;

namespace NzbDrone.Core.Tv
{
    public interface IEpisodeMonitoredService
    {
        void SetEpisodeMonitoredStatus(Series series, MonitoringOptions monitoringOptions);
    }

    public class EpisodeMonitoredService : IEpisodeMonitoredService
    {
        private readonly ISeriesService _seriesService;
        private readonly IEpisodeService _episodeService;
        private readonly Logger _logger;

        public EpisodeMonitoredService(ISeriesService seriesService, IEpisodeService episodeService, Logger logger)
        {
            _seriesService = seriesService;
            _episodeService = episodeService;
            _logger = logger;
        }

        public void SetEpisodeMonitoredStatus(Series series, MonitoringOptions monitoringOptions)
        {
            // Update the series without changing the episodes
            if (monitoringOptions == null)
            {
                _seriesService.UpdateSeries(series, false);
                return;
            }

            // Fallback for v2 endpoints
            if (monitoringOptions.Monitor == MonitorTypes.Unknown)
            {
                LegacySetEpisodeMonitoredStatus(series, monitoringOptions);
                return;
            }

            // Skip episode level monitoring and use season information when series was added
            if (monitoringOptions.Monitor == MonitorTypes.Skip)
            {
                return;
            }

            var firstSeason = series.Seasons.Select(s => s.SeasonNumber).Where(s => s > 0).MinOrDefault();
            var lastSeason = series.Seasons.Select(s => s.SeasonNumber).MaxOrDefault();
            var episodes = _episodeService.GetEpisodeBySeries(series.Id);

            switch (monitoringOptions.Monitor)
            {
                case MonitorTypes.All:
                    _logger.Debug("[{0}] Monitoring all episodes", series.Title);
                    ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0);

                    break;

                case MonitorTypes.Future:
                    _logger.Debug("[{0}] Monitoring future episodes", series.Title);
                    ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && (!e.AirDateUtc.HasValue || e.AirDateUtc >= DateTime.UtcNow));

                    break;

                case MonitorTypes.Missing:
                    _logger.Debug("[{0}] Monitoring missing episodes", series.Title);
                    ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && !e.HasFile);

                    break;

                case MonitorTypes.Existing:
                    _logger.Debug("[{0}] Monitoring existing episodes", series.Title);
                    ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && e.HasFile);

                    break;

                case MonitorTypes.Pilot:
                    _logger.Debug("[{0}] Monitoring first episode episodes", series.Title);
                    ToggleEpisodesMonitoredState(episodes,
                        e => e.SeasonNumber > 0 && e.SeasonNumber == firstSeason && e.EpisodeNumber == 1);

                    break;

                case MonitorTypes.FirstSeason:
                    _logger.Debug("[{0}] Monitoring first season episodes", series.Title);
                    ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && e.SeasonNumber == firstSeason);

                    break;

                case MonitorTypes.LastSeason:
                #pragma warning disable CS0612
                case MonitorTypes.LatestSeason:
                #pragma warning restore CS0612
                    _logger.Debug("[{0}] Monitoring latest season episodes", series.Title);

                    ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && e.SeasonNumber == lastSeason);

                    break;

                case MonitorTypes.Recent:
                    _logger.Debug("[{0}] Monitoring recent and future episodes", series.Title);

                    ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 &&
                                                                (!e.AirDateUtc.HasValue || (
                                                                        e.AirDateUtc.Value.Before(DateTime.UtcNow) &&
                                                                        e.AirDateUtc.Value.InLastDays(90))
                                                                    || e.AirDateUtc.Value.After(DateTime.UtcNow)));

                    break;

                case MonitorTypes.MonitorSpecials:
                    _logger.Debug("[{0}] Monitoring special episodes", series.Title);
                    ToggleEpisodesMonitoredState(episodes.Where(e => e.SeasonNumber == 0), true);

                    break;

                case MonitorTypes.UnmonitorSpecials:
                    _logger.Debug("[{0}] Unmonitoring special episodes", series.Title);
                    ToggleEpisodesMonitoredState(episodes.Where(e => e.SeasonNumber == 0), false);

                    break;

                case MonitorTypes.None:
                    _logger.Debug("[{0}] Unmonitoring all episodes", series.Title);
                    ToggleEpisodesMonitoredState(episodes, e => false);

                    break;
            }

            var monitoredSeasons = episodes.Where(e => e.Monitored)
                                           .Select(e => e.SeasonNumber)
                                           .Distinct()
                                           .ToList();

            foreach (var season in series.Seasons)
            {
                var seasonNumber = season.SeasonNumber;

                // Monitor the last season when:
                // - Not specials
                // - The latest season
                // - Set to monitor all episodes
                // - Set to monitor future episodes and series is continuing or not yet aired
                if (seasonNumber > 0 &&
                    seasonNumber == lastSeason &&
                    (monitoringOptions.Monitor == MonitorTypes.All ||
                     (monitoringOptions.Monitor == MonitorTypes.Future && series.Status is SeriesStatusType.Continuing or SeriesStatusType.Upcoming)))
                {
                    season.Monitored = true;
                }
                else if (seasonNumber == firstSeason && monitoringOptions.Monitor == MonitorTypes.Pilot)
                {
                    // Don't monitor season 1 if only the pilot episode is monitored
                    season.Monitored = false;
                }
                else if (monitoredSeasons.Contains(seasonNumber))
                {
                    // Monitor the season if it has any monitor episodes
                    season.Monitored = true;
                }

                // Don't monitor the season
                else
                {
                    season.Monitored = false;
                }
            }

            _episodeService.UpdateEpisodes(episodes);
            _seriesService.UpdateSeries(series, false);
        }

        private void LegacySetEpisodeMonitoredStatus(Series series, MonitoringOptions monitoringOptions)
        {
            _logger.Debug("[{0}] Setting episode monitored status.", series.Title);

            var episodes = _episodeService.GetEpisodeBySeries(series.Id);

            if (monitoringOptions.IgnoreEpisodesWithFiles)
            {
                _logger.Debug("Unmonitoring Episodes with Files");
                ToggleEpisodesMonitoredState(episodes.Where(e => e.HasFile), false);
            }
            else
            {
                _logger.Debug("Monitoring Episodes with Files");
                ToggleEpisodesMonitoredState(episodes.Where(e => e.HasFile), true);
            }

            if (monitoringOptions.IgnoreEpisodesWithoutFiles)
            {
                _logger.Debug("Unmonitoring Episodes without Files");
                ToggleEpisodesMonitoredState(episodes.Where(e => !e.HasFile && e.AirDateUtc.HasValue && e.AirDateUtc.Value.Before(DateTime.UtcNow)), false);
            }
            else
            {
                _logger.Debug("Monitoring Episodes without Files");
                ToggleEpisodesMonitoredState(episodes.Where(e => !e.HasFile && e.AirDateUtc.HasValue && e.AirDateUtc.Value.Before(DateTime.UtcNow)), true);
            }

            var lastSeason = series.Seasons.Select(s => s.SeasonNumber).MaxOrDefault();

            foreach (var s in series.Seasons)
            {
                var season = s;

                // If the season is unmonitored we should unmonitor all episodes in that season

                if (!season.Monitored)
                {
                    _logger.Debug("Unmonitoring all episodes in season {0}", season.SeasonNumber);
                    ToggleEpisodesMonitoredState(episodes.Where(e => e.SeasonNumber == season.SeasonNumber), false);
                }

                // If the season is not the latest season and all it's episodes are unmonitored the season will be unmonitored

                if (season.SeasonNumber < lastSeason)
                {
                    if (episodes.Where(e => e.SeasonNumber == season.SeasonNumber).All(e => !e.Monitored))
                    {
                        _logger.Debug("Unmonitoring season {0} because all episodes are not monitored", season.SeasonNumber);
                        season.Monitored = false;
                    }
                }
            }

            _episodeService.UpdateEpisodes(episodes);

            _seriesService.UpdateSeries(series, false);
        }

        private void ToggleEpisodesMonitoredState(IEnumerable<Episode> episodes, bool monitored)
        {
            foreach (var episode in episodes)
            {
                episode.Monitored = monitored;
            }
        }

        private void ToggleEpisodesMonitoredState(List<Episode> episodes, Func<Episode, bool> predicate)
        {
            ToggleEpisodesMonitoredState(episodes.Where(predicate), true);
            ToggleEpisodesMonitoredState(episodes.Where(e => !predicate(e)), false);
        }
    }
}
