using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using PlotLine.Models;
using PlotLine.Services;
using PlotLine.ViewModels;

namespace PlotLine.Controllers;

[Authorize]
public sealed class WarningsController(
    IContinuityValidationService validation,
    ISceneService scenes,
    ICompositeViewEngine viewEngine) : Controller
{
    [HttpGet]
    public async Task<IActionResult> SceneContinuityEditor(int sceneId)
    {
        var model = await BuildSceneContinuityEditorModelAsync(sceneId);
        return model is null
            ? NotFound()
            : PartialView("~/Views/Shared/SceneInspectorV2/_SceneContinuityEditor.cshtml", model);
    }

    public async Task<IActionResult> Index(
        int projectId,
        int? bookId,
        int? chapterId,
        int? sceneId,
        int? warningTypeId,
        int? warningSeverityId,
        string? entityType,
        string? category,
        bool includeDismissed = false,
        bool includeIntentional = false,
        bool showAcknowledgedWarnings = false)
    {
        var model = await validation.GetDashboardAsync(projectId, bookId, chapterId, sceneId, warningTypeId, warningSeverityId, entityType, category, includeDismissed, includeIntentional, showAcknowledgedWarnings);
        return model is null ? NotFound() : View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ValidateProject(int projectId, int? bookId)
    {
        var summary = await validation.ValidateProjectAsync(projectId);
        var model = await validation.GetDashboardAsync(projectId, bookId, null, null, null, null, null, null, false, false, false, summary);
        return model is null ? NotFound() : View("Index", model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ValidateBook(int projectId, int bookId)
    {
        var summary = await validation.ValidateBookAsync(bookId);
        var model = await validation.GetDashboardAsync(projectId, bookId, null, null, null, null, null, null, false, false, false, summary);
        return model is null ? NotFound() : View("Index", model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ValidateScene(int projectId, int? bookId, int sceneId, bool returnToTimeline = false)
    {
        await validation.ValidateSceneAsync(sceneId);
        if (IsSceneInspectorAjaxRequest())
        {
            return await SceneContinuityRefreshJsonAsync(sceneId);
        }

        if (TryGetLocalReturnUrl(out var returnUrl))
        {
            return LocalRedirect(returnUrl);
        }

        if (returnToTimeline)
        {
            return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId });
        }

        return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId });
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Dismiss(int id, int projectId, int? bookId, int? sceneId, bool returnToTimeline = false, int warningId = 0)
    {
        var continuityWarningId = warningId > 0 ? warningId : id;
        await validation.DismissAsync(continuityWarningId);
        if (sceneId.HasValue && IsSceneInspectorAjaxRequest())
        {
            return await SceneContinuityRefreshJsonAsync(sceneId.Value);
        }

        return RedirectAfterAction(projectId, bookId, sceneId, returnToTimeline);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> MarkIntentional(int id, int projectId, int? bookId, int? sceneId, bool returnToTimeline = false, int warningId = 0)
    {
        var continuityWarningId = warningId > 0 ? warningId : id;
        await validation.MarkIntentionalAsync(continuityWarningId);
        if (sceneId.HasValue && IsSceneInspectorAjaxRequest())
        {
            return await SceneContinuityRefreshJsonAsync(sceneId.Value);
        }

        return RedirectAfterAction(projectId, bookId, sceneId, returnToTimeline);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Restore(int id, int projectId, int? bookId, int? sceneId, int warningId = 0)
    {
        var continuityWarningId = warningId > 0 ? warningId : id;
        await validation.RestoreAsync(continuityWarningId);
        if (sceneId.HasValue && IsSceneInspectorAjaxRequest())
        {
            return await SceneContinuityRefreshJsonAsync(sceneId.Value);
        }

        return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId, includeDismissed = true, includeIntentional = true });
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> AcknowledgeGeneratedWarning(
        string warningType,
        int? sceneId,
        int? characterId,
        int? assetId,
        string? notes,
        [Bind(Prefix = "ReturnFilter")] WarningDashboardReturnFilter returnFilter)
    {
        if (returnFilter.ProjectID == 0 || string.IsNullOrWhiteSpace(warningType))
        {
            return BadRequest("Warning identity is required.");
        }

        await validation.AcknowledgeGeneratedWarningAsync(new ContinuityWarningAcknowledgement
        {
            ProjectID = returnFilter.ProjectID,
            WarningType = warningType,
            SceneID = sceneId,
            CharacterID = characterId,
            AssetID = assetId,
            Notes = notes
        });

        return RedirectToDashboard(returnFilter);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> RestoreGeneratedWarning(
        string warningType,
        int? sceneId,
        int? characterId,
        int? assetId,
        [Bind(Prefix = "ReturnFilter")] WarningDashboardReturnFilter returnFilter)
    {
        if (returnFilter.ProjectID == 0 || string.IsNullOrWhiteSpace(warningType))
        {
            return BadRequest("Warning identity is required.");
        }

        await validation.RestoreGeneratedWarningAsync(new ContinuityWarningAcknowledgement
        {
            ProjectID = returnFilter.ProjectID,
            WarningType = warningType,
            SceneID = sceneId,
            CharacterID = characterId,
            AssetID = assetId
        });

        return RedirectToDashboard(returnFilter);
    }

    private IActionResult RedirectAfterAction(int projectId, int? bookId, int? sceneId, bool returnToTimeline)
    {
        if (TryGetLocalReturnUrl(out var returnUrl))
        {
            return LocalRedirect(returnUrl);
        }

        if (returnToTimeline && sceneId.HasValue)
        {
            return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId.Value });
        }

        return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId });
    }

    private bool IsSceneInspectorAjaxRequest() =>
        string.Equals(Request.Headers["X-Requested-With"].ToString(), "XMLHttpRequest", StringComparison.OrdinalIgnoreCase)
        || Request.Headers.Accept.ToString().Contains("application/json", StringComparison.OrdinalIgnoreCase);

    private async Task<IActionResult> SceneContinuityRefreshJsonAsync(int sceneId)
    {
        var scene = await scenes.GetEditAsync(sceneId);
        var editorModel = scene is null ? null : await BuildSceneContinuityEditorModelAsync(scene);
        if (scene is null || editorModel is null)
        {
            return NotFound();
        }

        return Json(new
        {
            success = true,
            html = new
            {
                health = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneInspectorHealthStrip.cshtml", scene),
                continuity = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneContinuityCard.cshtml", scene)
            },
            editorHtml = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneContinuityEditor.cshtml", editorModel),
            scene = new
            {
                scene.SceneID
            }
        });
    }

    private async Task<SceneContinuityEditorViewModel?> BuildSceneContinuityEditorModelAsync(int sceneId)
    {
        var scene = await scenes.GetEditAsync(sceneId);
        return scene is null ? null : await BuildSceneContinuityEditorModelAsync(scene);
    }

    private async Task<SceneContinuityEditorViewModel?> BuildSceneContinuityEditorModelAsync(SceneEditViewModel scene)
    {
        var projectId = scene.ReturnProjectID ?? scene.Project?.ProjectID;
        if (!projectId.HasValue)
        {
            return null;
        }

        var dashboard = await validation.GetDashboardAsync(
            projectId.Value,
            scene.ReturnBookID ?? scene.Book?.BookID,
            null,
            scene.SceneID,
            null,
            null,
            null,
            null,
            includeDismissed: true,
            includeIntentional: true,
            showAcknowledgedWarnings: true);

        return dashboard is null
            ? null
            : new SceneContinuityEditorViewModel
            {
                Scene = scene,
                Dashboard = dashboard
            };
    }

    private async Task<string> RenderPartialViewToStringAsync(string viewName, object model)
    {
        var viewResult = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: false);
        if (!viewResult.Success)
        {
            viewResult = viewEngine.FindView(ControllerContext, viewName, isMainPage: false);
        }

        if (!viewResult.Success)
        {
            throw new InvalidOperationException($"Partial view '{viewName}' was not found.");
        }

        await using var writer = new StringWriter();
        var viewData = new ViewDataDictionary(ViewData)
        {
            Model = model
        };
        var viewContext = new ViewContext(
            ControllerContext,
            viewResult.View,
            viewData,
            TempData,
            writer,
            new HtmlHelperOptions());

        await viewResult.View.RenderAsync(viewContext);
        return writer.ToString();
    }

    private bool TryGetLocalReturnUrl(out string returnUrl)
    {
        if (Request.HasFormContentType
            && Request.Form.TryGetValue("ReturnUrl", out var postedReturnUrl)
            && Url.IsLocalUrl(postedReturnUrl.ToString()))
        {
            returnUrl = postedReturnUrl.ToString();
            return true;
        }

        returnUrl = string.Empty;
        return false;
    }

    private IActionResult RedirectToDashboard(WarningDashboardReturnFilter filter)
    {
        return RedirectToAction(nameof(Index), new
        {
            projectId = filter.ProjectID,
            bookId = filter.BookID,
            chapterId = filter.ChapterID,
            sceneId = filter.SceneID,
            warningTypeId = filter.WarningTypeID,
            warningSeverityId = filter.WarningSeverityID,
            entityType = filter.EntityType,
            category = filter.Category,
            includeDismissed = filter.IncludeDismissed,
            includeIntentional = filter.IncludeIntentional,
            showAcknowledgedWarnings = filter.ShowAcknowledgedWarnings
        });
    }
}
