using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using PlotLine.Models;
using PlotLine.Services;
using PlotLine.ViewModels;

namespace PlotLine.Controllers;

[Authorize]
public sealed class TimelineController(ITimelineService timeline) : Controller
{
    public async Task<IActionResult> Index([FromQuery] TimelineFilterViewModel filter)
    {
        if (filter.ProjectID == 0)
        {
            return BadRequest("ProjectID is required.");
        }

        var model = await timeline.GetAsync(filter);
        return model is null ? NotFound() : View(model);
    }

    public async Task<IActionResult> Settings(int projectId)
    {
        var model = await timeline.GetSettingsAsync(projectId);
        return model is null ? NotFound() : View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Settings(TimelineSettingsViewModel model)
    {
        if (model.ShowMetricShape && !model.SelectedMetricTypeIDs.Any())
        {
            ModelState.AddModelError(nameof(model.SelectedMetricTypeIDs), "Select at least one metric or disable Metric Shape.");
        }

        if (!ModelState.IsValid)
        {
            var current = await timeline.GetSettingsAsync(model.ProjectID);
            if (current is null)
            {
                return NotFound();
            }

            current.ShowSceneCards = model.ShowSceneCards;
            current.ShowMetricShape = model.ShowMetricShape;
            current.ShowPlotLines = model.ShowPlotLines;
            current.ShowStoryAssets = model.ShowStoryAssets;
            current.ShowCharacterAppearances = model.ShowCharacterAppearances;
            current.ShowWarnings = model.ShowWarnings;
            current.SelectedMetricTypeIDs = model.SelectedMetricTypeIDs;
            current.MetricOptions = current.MetricOptions
                .Select(x => new TimelineMetricSettingOptionViewModel
                {
                    MetricTypeID = x.MetricTypeID,
                    MetricName = x.MetricName,
                    Description = x.Description,
                    SortOrder = x.SortOrder,
                    IsSelected = model.SelectedMetricTypeIDs.Contains(x.MetricTypeID)
                })
                .ToList();
            return View(current);
        }

        await timeline.SaveSettingsAsync(model);
        TempData["TimelineSettingsMessage"] = "Timeline settings saved.";
        return RedirectToAction(nameof(Settings), new { projectId = model.ProjectID });
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> SavePreset(TimelinePresetSaveViewModel model)
    {
        await timeline.SavePresetAsync(model);
        return RedirectToAction(nameof(Index), new { projectId = model.ProjectID, bookId = model.BookID });
    }

    public async Task<IActionResult> LoadPreset(int id)
    {
        var preset = await timeline.GetPresetAsync(id);
        if (preset is null)
        {
            return NotFound();
        }

        var filter = System.Text.Json.JsonSerializer.Deserialize<TimelineFilterViewModel>(preset.FilterJson) ?? new TimelineFilterViewModel();
        filter.ProjectID = preset.ProjectID;
        filter.BookID = preset.BookID;
        filter.FocusType = preset.FocusType;
        filter.FocusID = preset.FocusID;
        return RedirectToAction(nameof(Index), filter);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DeletePreset(int id, int projectId, int? bookId)
    {
        await timeline.ArchivePresetAsync(id);
        return RedirectToAction(nameof(Index), new { projectId, bookId });
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DropMoveScene(TimelineSceneDropMoveViewModel model)
    {
        var summary = await timeline.MoveSceneByDropAsync(model);
        if (summary is not null)
        {
            TempData["TimelineDragMessage"] = $"Scene moved. Validation found {summary.WarningCount} warning{(summary.WarningCount == 1 ? "" : "s")}.";
        }
        return RedirectToAction(nameof(Index), new { projectId = model.ProjectID, bookId = model.BookID, selectedSceneId = model.ReturnSelectedSceneID });
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DropCharacter(TimelineSceneCharacterDropViewModel model)
    {
        await RunDropActionAsync(() => timeline.AddCharacterByDropAsync(model), "Character added to scene.");
        return RedirectToAction(nameof(Index), new { projectId = model.ReturnProjectID, bookId = model.ReturnBookID, selectedSceneId = model.SceneID });
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DropAssetEvent(TimelineAssetEventDropViewModel model)
    {
        await RunDropActionAsync(() => timeline.AddAssetEventByDropAsync(model), "Asset event added to scene.");
        return RedirectToAction(nameof(Index), new { projectId = model.ReturnProjectID, bookId = model.ReturnBookID, selectedSceneId = model.SceneID });
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DropAssetDependency(TimelineAssetDependencyDropViewModel model)
    {
        await RunDropActionAsync(() => timeline.AddAssetDependencyByDropAsync(model), "Asset dependency saved.");
        return RedirectToAction(nameof(Index), new { projectId = model.ReturnProjectID, bookId = model.ReturnBookID });
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DropLocationRelationship(TimelineLocationRelationshipDropViewModel model)
    {
        await RunDropActionAsync(() => timeline.AddLocationRelationshipByDropAsync(model), "Location relationship saved.");
        return RedirectToAction(nameof(Index), new { projectId = model.ProjectID, bookId = model.ReturnBookID });
    }

    private async Task RunDropActionAsync(Func<Task> action, string successMessage)
    {
        try
        {
            await action();
            TempData["TimelineDragMessage"] = successMessage;
        }
        catch (InvalidOperationException ex)
        {
            TempData["TimelineDragMessage"] = ex.Message;
        }
    }
}
