@using Microsoft.AspNetCore.Components.Web
@using Microsoft.EntityFrameworkCore
@using Microsoft.Extensions.Options
@using OpenArchival.Blazor.AdminPages
@using OpenArchival.DataAccess
@using OpenArchival.Blazor.CustomComponents
@using MudExRichTextEditor
@using MudBlazor
@using System.Text
@using Microsoft.AspNetCore.Components.Forms;
@using OpenArchival.Blazor.Config
@using OpenArchival.Blazor.AdminPages.Shared;

@page "/admin/blogedit"
@page "/admin/blogedit/{Id:int}"
@layout AdminControlPanelLayout;

<MudText Typo="Typo.h6">Create a Blog Post</MudText>
<MudDivider></MudDivider>
<MudTextField Placeholder="Title" T="string" @bind-Value=@BlogPostModel.Title></MudTextField>

<MudFileUpload T="IBrowserFile"
               AppendMultipleFiles=false
               Accept=".jpg,.png,.webp"
               MaximumFileCount="1"
               @bind-Files=_mainPhotoFile
               Class="mt-4">
    <ActivatorContent>
        <MudButton Variant="Variant.Filled"
                   Color="Color.Primary">
            Upload Main Photo
        </MudButton>
    </ActivatorContent>
    <SelectedTemplate>
        @if (context != null)
        {
            <MudText>@context.Name</MudText>
        }
        else if (BlogPostModel.MainPhoto != null)
        {
            <MudText>@BlogPostModel.MainPhoto.OriginalName</MudText>
        }
        else
        {
            <MudText>No Main Photo Selected</MudText>
        }
    </SelectedTemplate>
</MudFileUpload>

<MudExRichTextEdit @ref="@_contentEditor"
                   ReadOnly="false"
                   Height="444"
                   Class="m-2 mt-4"
                   Placeholder="Edit html"
                   ValueHtmlBehavior="MudExRichTextEditor.Types.GetHtmlBehavior.SemanticHtml"
                   @bind-Value="BlogPostModel.Content">
    @((MarkupString)BlogPostModel.Content)
</MudExRichTextEdit>

<MudText Typo="Typo.h6">Add Tags</MudText>
<MudDivider />
<MudText Typo="Typo.caption" Color="Color.Primary">Press enter to add.</MudText>
<ChipContainer T="BlogPostTag" @ref="@_tagsChipContainer" @bind-Items="BlogPostModel.Tags">
    <InputContent>
        <MudAutocomplete T="string"
                         Placeholder="Add Tags..."
                         @bind-Text=_tagsInputValue
                         OnKeyDown="HandleTagsEnter">
        </MudAutocomplete>
    </InputContent>
</ChipContainer>

<MudText Typo="Typo.h6" Class="mt-4">Link Artifact Groupings</MudText>
<MudDivider />
<MudText Typo="Typo.caption" Color="Color.Primary">Select Artifact Groupings to Associate with this post.</MudText>
<ArchiveGroupingsTable @ref=_artifactGroupingTable></ArchiveGroupingsTable>
<MudGrid Justify="Justify.FlexEnd" Class="mr-4 pt-8">
    <MudButton StartIcon="@Icons.Material.Filled.Publish" Color=Color.Primary Variant=Variant.Filled OnClick="OnPublishBlogPost">Publish</MudButton>
</MudGrid>

@inject IOptions<FileUploadOptions> FileOptions;
@inject IDbContextFactory<ApplicationDbContext> ContextFactory;
@inject ISnackbar Snackbar;
@inject NavigationManager NavigationManager;

@*OnKeyDown="@(ev => HandleChipContainerEnter<string>(ev, _tagsChipContainer, _tagsInputValue, () => _tagsInputValue = string.Empty))"*@
@code {
    [Parameter]
    public int Id { get; set; }

    public List<string> StringTags { get; set; } = [];
    public BlogPost BlogPostModel { get; set; } = new();

    private MudExRichTextEdit _contentEditor = default!;

    private string _tagsInputValue = "";
    private ChipContainer<BlogPostTag> _tagsChipContainer = default!;

    private ArchiveGroupingsTable _artifactGroupingTable = default!;

    private IBrowserFile? _mainPhotoFile;

    protected override void OnParametersSet()
    {
        if (Id != 0)
        {
            using var context = ContextFactory.CreateDbContext();

            BlogPost? existingPost = context.BlogPosts
                .Include(p => p.Tags)
                .Include(p=>p.ArtifactGroupings)
                    .ThenInclude(g=>g.Category)
                .Include(p=>p.MainPhoto)
                .Where(p => p.Id == Id)
                .FirstOrDefault();

            if (existingPost is null)
            {
                NavigationManager.NavigateTo($"blog-not-found/{System.Web.HttpUtility.UrlEncode($"No blog with id {Id} exists in the database!")}");
                return;
            }

            BlogPostModel = existingPost;
        }
        base.OnParametersSet();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        // We only want to run this logic ONCE, on the very first render.
        if (firstRender)
        {
            // Check if we are in "edit" mode (Id != 0) and the loaded post
            // actually has any groupings to select.
            if (Id != 0 && BlogPostModel.ArtifactGroupings.Any())
            {
                // Now it is safe to call the method because _artifactGroupingTable
                // has been rendered and is no longer null.
                await _artifactGroupingTable.SetSelectedItemsAsync(BlogPostModel.ArtifactGroupings);

                // We call StateHasChanged() to ensure the UI updates
                // with the selection state we just pushed to the child.
                StateHasChanged();
            }
        }

        await base.OnAfterRenderAsync(firstRender);
    }

    private async Task DeDuplicatePostTags(BlogPost post, ApplicationDbContext context)
    {
        List<BlogPostTag> finalTags = [];
        foreach (BlogPostTag tag in post.Tags)
        {
            var existing = await context.BlogPostTags.Where(t => t.Name == tag.Name).FirstOrDefaultAsync();
            if (existing is not null)
            {
                finalTags.Add(existing);
            }
            else
            {
                context.BlogPostTags.Add(tag);
                finalTags.Add(tag);
            }
        }

        post.Tags = finalTags;
    }

    private void BuildBlogSearchIndex(BlogPost post)
    {
        var tagsSb = new StringBuilder();
        var allSb = new StringBuilder();

        allSb.Append($"{post.Title} ");
        allSb.Append($"{post.Content} ");

        foreach (BlogPostTag tag in post.Tags)
        {
            tagsSb.Append($"{tag.Name} ");
            allSb.Append($"{tag.Name} ");
        }

        post.AllSearchString = allSb.ToString();
        post.TagsSearchString = tagsSb.ToString();
    }

    private async Task OnPublishBlogPost(Microsoft.AspNetCore.Components.Web.MouseEventArgs args)
    {
        // ... (content and title checks) ...

        await using var context = await ContextFactory.CreateDbContextAsync();

        bool isCreatingNewPost = BlogPostModel.Id == 0;
        FilePathListing? newPhoto = null; // Will hold the new photo entity, if one is uploaded
        FilePathListing? oldPhotoToDelete = null; // Will hold the old photo entity for deletion

        // --- 1. Handle New Photo Upload ---
        if (_mainPhotoFile is not null)
        {
            // A new file was uploaded.
            try
            {
                var diskFileName = $"{Guid.NewGuid()}{Path.GetExtension(_mainPhotoFile.Name)}";
                var destinationPath = Path.Combine(FileOptions.Value.UploadFolderPath, diskFileName);

                await using var browserUploadStream = _mainPhotoFile.OpenReadStream(maxAllowedSize: FileOptions.Value.MaxUploadSizeBytes);
                await using var outFileStream = new FileStream(destinationPath, FileMode.Create);
                await browserUploadStream.CopyToAsync(outFileStream);

                // Create the new entity
                newPhoto = new FilePathListing() { Path = destinationPath, OriginalName = Path.GetFileName(_mainPhotoFile.Name) };

                // Add it to the context so it's tracked
                context.ArtifactFilePaths.Add(newPhoto);

                // We update the model's reference just in case
                BlogPostModel.MainPhoto = newPhoto;

                Snackbar.Add($"Uploaded {_mainPhotoFile.Name}", Severity.Success);
            }
            catch (Exception ex)
            {
                Snackbar.Add($"Error uploading file {_mainPhotoFile.Name}: {ex.Message}", Severity.Error);
                return;
            }
        }
        else if (isCreatingNewPost)
        {
            // Case: Creating a new post, but no file was provided. This is an error.
            Snackbar.Add("A main photo is required", Severity.Warning);
            return;
        }

        // --- 2. Database Save Logic ---
        BlogPostModel.ModifiedTime = DateTime.UtcNow;
        BlogPostModel.ArtifactGroupings = await _artifactGroupingTable.SelectedItems();

        foreach (ArtifactGrouping grouping in BlogPostModel.ArtifactGroupings)
        {
            // This tells EF "this object already exists in the database."
            // It will attach it in an "Unchanged" state without
            // causing conflicts with its related entities.
            context.Entry(grouping).State = EntityState.Unchanged;
        }

        if (isCreatingNewPost)
        {
            BlogPostModel.CreationTime = DateTime.UtcNow;
            await DeDuplicatePostTags(BlogPostModel, context);
            BuildBlogSearchIndex(BlogPostModel);

            // Assign the new (and tracked) photo to the new post
            BlogPostModel.MainPhoto = newPhoto!;
            context.BlogPosts.Add(BlogPostModel);
        }
        else
        {
            // --- 3. Update Existing Post ---
            BlogPost? existing = await context.BlogPosts
                .Include(p => p.Tags)
                .Include(p => p.MainPhoto) // Load the existing photo
                .Include(p=>p.ArtifactGroupings)
                .FirstOrDefaultAsync(p => p.Id == BlogPostModel.Id);

            if (existing is null)
            {
                Snackbar.Add($"Unable to find a post with id {BlogPostModel.Id} in the database, cannot update post!", Severity.Error);
                return;
            }

            await DeDuplicatePostTags(BlogPostModel, context);

            existing.Title = BlogPostModel.Title;
            existing.Content = BlogPostModel.Content;
            existing.ModifiedTime = BlogPostModel.ModifiedTime;
            existing.ArtifactGroupings = BlogPostModel.ArtifactGroupings;

            // --- 4. Handle Photo Swap (THIS IS THE FIX) ---
            if (newPhoto is not null)
            {
                // A new photo was uploaded.
                // Save the *tracked* old photo for deletion later.
                if (existing.MainPhoto is not null)
                {
                    oldPhotoToDelete = existing.MainPhoto;
                }

                // Assign the *new, tracked* photo.
                existing.MainPhoto = newPhoto;
            }
            // If newPhoto is null, we do nothing. existing.MainPhoto remains unchanged.
            // This avoids the tracking conflict.

            existing.Tags.Clear();
            foreach (var tag in BlogPostModel.Tags)
            {
                existing.Tags.Add(tag);
            }

            BuildBlogSearchIndex(existing);
        }

        // --- 5. Save Primary Changes ---
        await context.SaveChangesAsync();

        // --- 6. Old File Cleanup ---
        if (oldPhotoToDelete is not null)
        {
            // Now that the DB save is successful, delete the old file
            try
            {
                if (File.Exists(oldPhotoToDelete.Path))
                {
                    File.Delete(oldPhotoToDelete.Path);
                }

                // And remove its record (which is tracked) from the database
                context.ArtifactFilePaths.Remove(oldPhotoToDelete);
                await context.SaveChangesAsync();
            }
            catch (Exception ex)
            {
                Snackbar.Add($"Error deleting old photo: {ex.Message}", Severity.Error);
            }
        }

        Snackbar.Add("Changes saved!", Severity.Success);

        // --- 7. Reset Page ---
        BlogPostModel = new();
        _mainPhotoFile = null;

        // We can't navigate yet, we need to clear the groupings
        await _artifactGroupingTable.SetSelectedItemsAsync(new List<ArtifactGrouping>());

        StateHasChanged();

        NavigationManager.NavigateTo($"/admin/blogstable");
    }

    public void HandleTagsEnter(KeyboardEventArgs args)
    {
        if (args.Key == "Enter")
        {
            var newTag = new BlogPostTag() { Name = _tagsInputValue };
            _tagsInputValue = "";

            BlogPostModel.Tags.Add(newTag);

            StateHasChanged();
        }
    }

}
