using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace YTManager.Controllers {
    [Produces("application/json")]
    [Route("api/Videos")]
    [EnableCors("AllowAllOrigins")]
    public class VideosController : Controller {
        // Custom return type for API accesses. Done this way to ensure we
        // always return the expected data regardless of the underlying model.
        struct Video_ForAPI {
            // Title of the video.
            public string Title;

            // Description of the video.
            public string Description;

            // Youtube ID of the video.
            public string ID;

            // Thumbnail URL.
            public string Thumbnail;

            // Channel on youtube that owns this video
            public string Channel;

            // Duration of the video in seconds.
            public int Seconds;

            // What tags are relevant with this video.
            public List<string> Tags;

            // Populate this struct using a model video.
            public Video_ForAPI(Models.Video video) {
                Title = video.Title;
                Description = video.Description;
                ID = video.YoutubeID;
                Thumbnail = video.ThumbnailURL;
                Channel = video.Channel.Title;
                Seconds = (int)video.Duration.TotalSeconds;
                Tags = video.Tags?.Select(t => t.Name).ToList();
            }
        }

        // Maximum number of entries to return per query.
        private readonly int max_per_query = 18;

        // DB context used for all these calls.
        private readonly MediaDB db;

        // Constructor to fetch the db context.
        public VideosController(MediaDB context) => db = context;

        // Returns the most recent videos.
        [HttpGet]
        public async Task<IActionResult> GetVideos() {
            // Get all the relevant videos.
            var vids = await db.Videos
                .OrderByDescending(i => i.AddedToYT)
                .Take(max_per_query)
                .Include(v => v.Channel)
                .Include(v => v.Tags)
                .ToListAsync();

            // Convert them to what we will send out.
            var converted = vids
                .Select(v => new Video_ForAPI(v))
                .ToList();

            // Convert all the videos to what we will send back.
            return Ok(converted);
        }

        struct Search_Query
        {
            // Tags.
            public List<Models.Tag> Tags;

            // What the duration type is.
            public enum _Duration_Type { LessThan, GreaterThan, Unset };
            public _Duration_Type Duration_Type;

            // What the expected duration of the videos are.
            public TimeSpan Duration;

            // Remaining tokens.
            public List<String> Remaining_Tokens;
        }
        

        // Searches for videos using the specified tag(s).
        [HttpGet("search/{searchstr}")]
        public async Task<IActionResult> Search([FromRoute] string searchstr) {
            // What to use for searching videos with.
            var parsed_query = new Search_Query();

            // Assume the search string is comma seperated.
            parsed_query.Remaining_Tokens = searchstr.Split(',').Select(t => t.Trim().ToLower()).ToList();

            // Find a potential single video duration.
            List<string> greaterthan = parsed_query.Remaining_Tokens.Where(token => token.StartsWith('>')).ToList();
            List<string> lessthan = parsed_query.Remaining_Tokens.Where(token => token.StartsWith('<')).ToList();
            if (greaterthan.Count() + lessthan.Count() > 1)
                return BadRequest();
            if (greaterthan.Count() == 1) parsed_query.Duration_Type = Search_Query._Duration_Type.GreaterThan;
            else if (lessthan.Count() == 1) parsed_query.Duration_Type = Search_Query._Duration_Type.LessThan;
            else parsed_query.Duration_Type = Search_Query._Duration_Type.Unset;
            greaterthan.ForEach(g => parsed_query.Remaining_Tokens.Remove(g));
            lessthan.ForEach(l => parsed_query.Remaining_Tokens.Remove(l));

            // Attempt to parse it using ISO8601 duration.
            if (parsed_query.Duration_Type != Search_Query._Duration_Type.Unset) {
                string timingstr = "PT" + greaterthan
                    .Concat(lessthan)
                    .Select(s => s.TrimStart(new char[] { '<', '>' }))
                    .First();

                try {
                    parsed_query.Duration = XmlConvert.ToTimeSpan(timingstr.ToUpper());
                } catch (Exception) {
                    return BadRequest("Failed to parse duration.");
                }
            }

            // Find which tokens are explicit tags
            parsed_query.Tags = await db.Tags
                .Where(t => parsed_query.Remaining_Tokens.Any(token => token == t.Name.ToLower()))
                .ToListAsync();
            parsed_query.Tags.ForEach(tag => parsed_query.Remaining_Tokens.Remove(tag.Name.ToLower()));

            // Get from the database all videos which satisfy the query via
            // AND'ing all the queries.
            var dbquery = db.Videos.Select(v => v);

            // Get all videos that satisfy the time duration.
            if (parsed_query.Duration_Type == Search_Query._Duration_Type.GreaterThan)
                dbquery = dbquery.Where(v => v.Duration >= parsed_query.Duration);
            else if (parsed_query.Duration_Type == Search_Query._Duration_Type.LessThan)
                dbquery = dbquery.Where(v => v.Duration <= parsed_query.Duration);

            // Match videos where the tag matches.
            parsed_query.Tags.ForEach(tag => dbquery = dbquery.Where(V => V.Tags.Any(vt => vt.Name == tag.Name)));

            // Get all videos that match their title, description, or channel name 
            // with the remaining tokens.
            parsed_query.Remaining_Tokens.ForEach(token => {
                dbquery = dbquery.Where(v => 
                    v.Channel.Title.ToLower().Contains(token) || 
                    v.Title.ToLower().Contains(token) || 
                    v.Description.ToLower().Contains(token));
            });

            // Get all the relevant videos.
            var vids = await dbquery
                .OrderByDescending(i => i.AddedToYT)
                .Take(max_per_query)
                .Include(v => v.Channel)
                .Include(v => v.Tags)
                .ToListAsync();

            // Convert them to what we will send out.
            var converted = vids
                .Select(v => new Video_ForAPI(v))
                .ToList();

            // Convert all the videos to what we will send back.
            return Ok(converted);
        }

        // Returns the most recent videos of a channel.
        [HttpGet("fromchannel/{channelName}")]
        public async Task<IActionResult> Get_Channel_Videos([FromRoute] string channelName) {
            // Get all the relevant videos.
            var vids = await db.Videos
                .Where(v => v.Channel.Title == channelName)
                .OrderByDescending(i => i.AddedToYT)
                .Take(max_per_query)
                .Include(v => v.Channel)
                .Include(v => v.Tags)
                .ToListAsync();

            // Convert them to what we will send out.
            var converted = vids
                .Select(v => new Video_ForAPI(v))
                .ToList();

            // Convert all the videos to what we will send back.
            return Ok(converted);
        }
    }
}