﻿using KernelRecordsMVC.Application.ViewModels;
using KernelRecordsMVC.Domain.Enums;
using KernelRecordsMVC.Infrastructure.Data;
using KernelRecordsMVC.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace KernelRecordsMVC.Web.Controllers;

public class AdminController : Controller
{
    private readonly KernelRecordsContext _context;

    public AdminController(KernelRecordsContext context)
    {
        _context = context;
    }


    // =========================================================
    // ADMIN DASHBOARD
    // =========================================================

    [HttpGet]
    public IActionResult Index()
    {
        if (!IsAdmin())
            return Forbid();


        ViewBag.ProductCount =
            _context.Products.Count();

        ViewBag.ReleaseCount =
            _context.Releases.Count();

        ViewBag.OutOfStockCount =
            _context.Products.Count(x =>
                x.Stock <= 0);

        ViewBag.LowStockCount =
            _context.Products.Count(x =>
                x.Stock > 0 &&
                x.Stock <= 5);


        return View();
    }


    // =========================================================
    // PRODUCTS
    // =========================================================

    [HttpGet]
    public IActionResult Products()
    {
        if (!IsProductManager())
            return Forbid();


        var products = _context.Products
            .Include(p => p.Release)
            .OrderBy(p => p.Release.Title)
            .ThenBy(p => p.Format)
            .ToList();


        return View(products);
    }


    // =========================================================
    // CREATE PRODUCT - GET
    // =========================================================

    [HttpGet]
    public IActionResult CreateProduct()
    {
        if (!IsProductManager())
            return Forbid();


        LoadReleases();


        return View(
            new CreateProductViewModel());
    }


    // =========================================================
    // CREATE PRODUCT - POST
    // =========================================================

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult CreateProduct(
        CreateProductViewModel model)
    {
        if (!IsProductManager())
            return Forbid();


        if (!ModelState.IsValid)
        {
            LoadReleases();

            return View(model);
        }


        var release = _context.Releases
            .FirstOrDefault(x =>
                x.ReleaseId == model.ReleaseId);


        if (release == null)
        {
            ModelState.AddModelError(
                nameof(model.ReleaseId),
                "The selected release does not exist.");


            LoadReleases();


            return View(model);
        }


        var alreadyExists =
            _context.Products.Any(p =>
                p.ReleaseId == model.ReleaseId &&
                p.Format == model.Format);


        if (alreadyExists)
        {
            ModelState.AddModelError(
                nameof(model.Format),
                "This release already has a product in this format.");


            LoadReleases();


            return View(model);
        }


        using var transaction =
            _context.Database.BeginTransaction();


        try
        {
            var product = new Product
            {
                ProductId =
                    GetNextProductId(),

                ReleaseId =
                    model.ReleaseId,

                Format =
                    model.Format,

                Price =
                    model.Price,

                ProductDescription =
                    model.ProductDescription,

                Stock =
                    model.Stock
            };


            _context.Products.Add(product);

            _context.SaveChanges();


            CreateModification(
                ModificationType.CREATE,
                product.ProductId);


            transaction.Commit();


            TempData["Success"] =
                $"{release.Title} ({product.Format}) was created successfully.";


            return RedirectToAction(
                nameof(Products));
        }
        catch
        {
            transaction.Rollback();

            throw;
        }
    }


    // =========================================================
    // EDIT PRODUCT - GET
    // =========================================================

    [HttpGet]
    public IActionResult EditProduct(long id)
    {
        if (!IsProductManager())
            return Forbid();


        var product = _context.Products
            .Include(p => p.Release)
            .FirstOrDefault(p =>
                p.ProductId == id);


        if (product == null)
            return NotFound();


        var model =
            new EditProductViewModel
            {
                ProductId =
                    product.ProductId,

                ReleaseId =
                    product.ReleaseId,

                ReleaseTitle =
                    product.Release.Title,

                Format =
                    product.Format,

                Price =
                    product.Price,

                ProductDescription =
                    product.ProductDescription,

                Stock =
                    product.Stock,

                ModificationType =
                    ModificationType.UPDATE
            };


        return View(model);
    }


    // =========================================================
    // EDIT PRODUCT - POST
    // =========================================================

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult EditProduct(
        EditProductViewModel model)
    {
        if (!IsProductManager())
            return Forbid();


        var product = _context.Products
            .Include(p => p.Release)
            .FirstOrDefault(p =>
                p.ProductId == model.ProductId);


        if (product == null)
            return NotFound();


        // These values come from the database.
        model.ReleaseId =
            product.ReleaseId;

        model.ReleaseTitle =
            product.Release.Title;

        model.Format =
            product.Format;


        // Display-only fields should not block
        // validation of the submitted form.
        ModelState.Remove(
            nameof(model.ReleaseId));

        ModelState.Remove(
            nameof(model.ReleaseTitle));

        ModelState.Remove(
            nameof(model.Format));


        // =====================================================
        // DISCOUNT VALIDATION
        // =====================================================

        if (model.ModificationType ==
            ModificationType.DISCOUNT)
        {
            if (!model.Discount.HasValue)
            {
                ModelState.AddModelError(
                    nameof(model.Discount),
                    "Please enter a discount percentage.");
            }
            else if (
                model.Discount.Value <= 0 ||
                model.Discount.Value >= 100)
            {
                ModelState.AddModelError(
                    nameof(model.Discount),
                    "Discount must be greater than 0 and less than 100.");
            }
        }


        if (!ModelState.IsValid)
            return View(model);


        using var transaction =
            _context.Database.BeginTransaction();


        try
        {
            // =================================================
            // APPLY DISCOUNT
            // =================================================

            if (model.ModificationType ==
                ModificationType.DISCOUNT)
            {
                var discountPercentage =
                    model.Discount!.Value;


                var oldPrice =
                    product.Price;


                var discountAmount =
                    oldPrice *
                    (discountPercentage / 100m);


                var newPrice =
                    oldPrice - discountAmount;


                newPrice =
                    Math.Round(
                        newPrice,
                        2,
                        MidpointRounding.AwayFromZero);


                // Product.Price becomes the
                // actual current sale price.
                product.Price =
                    newPrice;


                _context.Products.Update(
                    product);

                _context.SaveChanges();


                CreateModification(
                    ModificationType.DISCOUNT,
                    product.ProductId,
                    discountPercentage);


                transaction.Commit();


                TempData["Success"] =
                    $"{discountPercentage:0.##}% discount applied. " +
                    $"Price changed from {oldPrice:C} to {newPrice:C}.";


                return RedirectToAction(
                    nameof(Products));
            }


            // =================================================
            // NORMAL UPDATE
            // =================================================

            product.Price =
                model.Price;

            product.ProductDescription =
                model.ProductDescription;

            product.Stock =
                model.Stock;


            _context.Products.Update(
                product);

            _context.SaveChanges();


            CreateModification(
                ModificationType.UPDATE,
                product.ProductId);


            transaction.Commit();


            TempData["Success"] =
                $"{product.Release.Title} was updated successfully.";


            return RedirectToAction(
                nameof(Products));
        }
        catch
        {
            transaction.Rollback();

            throw;
        }
    }


    // =========================================================
    // CREATE RELEASE - GET
    // =========================================================

    [HttpGet]
    public IActionResult CreateRelease()
    {
        if (!IsAdmin())
            return Forbid();


        LoadArtists();


        var model =
            new CreateReleaseViewModel
            {
                ReleaseDate =
                    DateTime.Today,

                ReleaseType =
                    "ALBUM",

                // Start an album with one empty track.
                Tracks =
                    new List<CreateTrackViewModel>
                    {
                        new CreateTrackViewModel()
                    }
            };


        return View(model);
    }


    // =========================================================
    // CREATE RELEASE - POST
    // =========================================================

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult CreateRelease(
        CreateReleaseViewModel model)
    {
        if (!IsAdmin())
            return Forbid();


        // =====================================================
        // RELEASE TYPE VALIDATION
        // =====================================================

        if (model.ReleaseType != "ALBUM" &&
            model.ReleaseType != "SINGLE")
        {
            ModelState.AddModelError(
                nameof(model.ReleaseType),
                "Please select Album or Single.");
        }


        // =====================================================
        // MAIN ARTIST VALIDATION
        // =====================================================

        var mainArtistExists =
            _context.Artists.Any(a =>
                a.ArtistId ==
                model.MainArtistId);


        if (!mainArtistExists)
        {
            ModelState.AddModelError(
                nameof(model.MainArtistId),
                "Please select a valid main artist.");
        }


        // =====================================================
        // FEATURED ARTISTS
        // =====================================================

        model.FeaturedArtistIds ??=
            new List<long>();


        model.FeaturedArtistIds =
            model.FeaturedArtistIds
                .Distinct()
                .ToList();


        // Do not allow the main artist
        // to also be a featured artist.
        model.FeaturedArtistIds.Remove(
            model.MainArtistId);


        // Validate that featured artists exist.
        if (model.FeaturedArtistIds.Count > 0)
        {
            var validFeaturedArtistCount =
                _context.Artists.Count(a =>
                    model.FeaturedArtistIds
                        .Contains(a.ArtistId));


            if (validFeaturedArtistCount !=
                model.FeaturedArtistIds.Count)
            {
                ModelState.AddModelError(
                    nameof(model.FeaturedArtistIds),
                    "One or more featured artists are invalid.");
            }
        }


        // =====================================================
        // SINGLE VALIDATION
        // =====================================================

        if (model.ReleaseType == "SINGLE")
        {
            if (string.IsNullOrWhiteSpace(
                model.SingleDuration))
            {
                ModelState.AddModelError(
                    nameof(model.SingleDuration),
                    "Duration is required for a single.");
            }
        }


        // =====================================================
        // ALBUM VALIDATION
        // =====================================================

        if (model.ReleaseType == "ALBUM")
        {
            model.Tracks ??=
                new List<CreateTrackViewModel>();


            // Remove completely empty rows.
            model.Tracks =
                model.Tracks
                    .Where(t =>
                        !string.IsNullOrWhiteSpace(
                            t.SongName) ||
                        !string.IsNullOrWhiteSpace(
                            t.SongDuration))
                    .ToList();


            if (model.Tracks.Count == 0)
            {
                ModelState.AddModelError(
                    nameof(model.Tracks),
                    "An album must contain at least one track.");
            }


            for (var i = 0;
                 i < model.Tracks.Count;
                 i++)
            {
                var track =
                    model.Tracks[i];


                if (string.IsNullOrWhiteSpace(
                    track.SongName))
                {
                    ModelState.AddModelError(
                        $"Tracks[{i}].SongName",
                        "Track name is required.");
                }


                if (string.IsNullOrWhiteSpace(
                    track.SongDuration))
                {
                    ModelState.AddModelError(
                        $"Tracks[{i}].SongDuration",
                        "Track duration is required.");
                }


                track.ArtistIds ??=
                    new List<long>();


                track.ArtistIds =
                    track.ArtistIds
                        .Distinct()
                        .ToList();


                // Validate selected track artists.
                if (track.ArtistIds.Count > 0)
                {
                    var validTrackArtistCount =
                        _context.Artists.Count(a =>
                            track.ArtistIds.Contains(
                                a.ArtistId));


                    if (validTrackArtistCount !=
                        track.ArtistIds.Count)
                    {
                        ModelState.AddModelError(
                            $"Tracks[{i}].ArtistIds",
                            "One or more selected track artists are invalid.");
                    }
                }
            }
        }


        if (!ModelState.IsValid)
        {
            LoadArtists();

            return View(model);
        }


        using var transaction =
            _context.Database.BeginTransaction();


        try
        {
            // =================================================
            // CREATE RELEASE
            // =================================================

            var release =
                new Release
                {
                    ReleaseId =
                        GetNextReleaseId(),

                    Title =
                        model.Title.Trim(),

                    RecordLabel =
                        string.IsNullOrWhiteSpace(
                            model.RecordLabel)
                            ? null
                            : model.RecordLabel.Trim(),

                    Genre =
                        model.Genre.Trim(),

                    ReleaseDate =
                        model.ReleaseDate,

                    CoverPhoto =
                        model.CoverPhoto.Trim()
                };


            _context.Releases.Add(
                release);

            _context.SaveChanges();


            // =================================================
            // MAIN RELEASE ARTIST
            // =================================================

            var mainReleaseArtist =
                new ReleaseArtist
                {
                    ReleaseId =
                        release.ReleaseId,

                    ArtistId =
                        model.MainArtistId,

                    ReleaseOrdinal = 1,

                    Type =
                        ArtistReleaseType.MAIN
                };


            _context.ReleaseArtists.Add(
                mainReleaseArtist);


            // =================================================
            // FEATURED RELEASE ARTISTS
            // =================================================

            long releaseOrdinal = 2;


            foreach (var artistId
                     in model.FeaturedArtistIds)
            {
                var featuredArtist =
                    new ReleaseArtist
                    {
                        ReleaseId =
                            release.ReleaseId,

                        ArtistId =
                            artistId,

                        ReleaseOrdinal =
                            releaseOrdinal++,

                        Type =
                            ArtistReleaseType.FEATURE
                    };


                _context.ReleaseArtists.Add(
                    featuredArtist);
            }


            _context.SaveChanges();


            // =================================================
            // ALBUM
            // =================================================

            if (model.ReleaseType == "ALBUM")
            {
                var album =
                    new Album
                    {
                        ReleaseId =
                            release.ReleaseId
                    };


                _context.Albums.Add(
                    album);

                _context.SaveChanges();


                // =============================================
                // TRACKS
                // =============================================

                foreach (var trackModel
                         in model.Tracks)
                {
                    var song =
                        new Song
                        {
                            SongId =
                                GetNextSongId(),

                            SongName =
                                trackModel
                                    .SongName
                                    .Trim(),

                            SongDuration =
                                trackModel
                                    .SongDuration
                                    .Trim()
                        };


                    _context.Songs.Add(
                        song);

                    _context.SaveChanges();


                    // =========================================
                    // LINK SONG TO ALBUM
                    // =========================================

                    var albumSong =
                        new AlbumSong
                        {
                            AlbumId =
                                release.ReleaseId,

                            SongId =
                                song.SongId
                        };


                    _context.AlbumSongs.Add(
                        albumSong);


                    // =========================================
                    // SONG ARTISTS
                    // =========================================

                    var trackArtistIds =
                        trackModel.ArtistIds
                            .Distinct()
                            .ToList();


                    // If no track artists were selected,
                    // automatically use the release's
                    // main artist.
                    if (trackArtistIds.Count == 0)
                    {
                        trackArtistIds.Add(
                            model.MainArtistId);
                    }


                    long songOrdinal = 1;


                    foreach (var artistId
                             in trackArtistIds)
                    {
                        var songArtist =
                            new SongArtist
                            {
                                SongId =
                                    song.SongId,

                                ArtistId =
                                    artistId,

                                SongOrdinal =
                                    songOrdinal++
                            };


                        _context.SongArtists.Add(
                            songArtist);
                    }


                    _context.SaveChanges();
                }
            }


            // =================================================
            // SINGLE
            // =================================================

            else
            {
                var single =
                    new SingleRelease
                    {
                        ReleaseId =
                            release.ReleaseId,

                        Duration =
                            model.SingleDuration!
                                .Trim()
                    };


                _context.SingleReleases.Add(
                    single);

                _context.SaveChanges();
            }


            transaction.Commit();


            TempData["Success"] =
                $"{release.Title} was created successfully.";


            return RedirectToAction(
                nameof(Index));
        }
        catch
        {
            transaction.Rollback();

            throw;
        }
    }


    // =========================================================
    // AUTHORIZATION HELPERS
    // =========================================================

    private bool IsAdmin()
    {
        return HttpContext.Session
            .GetString("Role") ==
            "Admin";
    }


    private bool IsProductManager()
    {
        var role =
            HttpContext.Session
                .GetString("Role");

        var adminType =
            HttpContext.Session
                .GetString("AdminType");


        return role == "Admin" &&
               (
                   adminType == "PRODUCT_MANAGER" ||
                   adminType == "SUPER_ADMIN"
               );
    }


    // =========================================================
    // CURRENT ADMIN ID
    // =========================================================

    private long? GetCurrentUserId()
    {
        var userId =
            HttpContext.Session
                .GetInt32("UserId");


        if (!userId.HasValue)
            return null;


        return userId.Value;
    }


    // =========================================================
    // LOAD RELEASES
    // =========================================================

    private void LoadReleases()
    {
        ViewBag.Releases =
            _context.Releases
                .OrderBy(x =>
                    x.Title)
                .ToList();
    }


    // =========================================================
    // LOAD ARTISTS
    // =========================================================

    private void LoadArtists()
    {
        ViewBag.Artists =
            _context.Artists
                .OrderBy(a =>
                    a.ArtistName)
                .ToList();
    }


    // =========================================================
    // NEXT PRODUCT ID
    // =========================================================

    private long GetNextProductId()
    {
        var maxId =
            _context.Products
                .Select(x =>
                    (long?)x.ProductId)
                .Max();


        return (maxId ?? 0) + 1;
    }


    // =========================================================
    // NEXT RELEASE ID
    // =========================================================

    private long GetNextReleaseId()
    {
        var maxId =
            _context.Releases
                .Select(x =>
                    (long?)x.ReleaseId)
                .Max();


        return (maxId ?? 0) + 1;
    }


    // =========================================================
    // NEXT SONG ID
    // =========================================================

    private long GetNextSongId()
    {
        var maxId =
            _context.Songs
                .Select(x =>
                    (long?)x.SongId)
                .Max();


        return (maxId ?? 0) + 1;
    }


    // =========================================================
    // NEXT MODIFICATION ID
    // =========================================================

    private long GetNextModificationId()
    {
        var maxId =
            _context.Modifications
                .Select(x =>
                    (long?)x.ModificationId)
                .Max();


        return (maxId ?? 0) + 1;
    }


    // =========================================================
    // CREATE MODIFICATION RECORD
    // =========================================================

    private void CreateModification(
        ModificationType type,
        long productId,
        decimal? discount = null)
    {
        var adminId =
            GetCurrentUserId();


        if (adminId == null)
        {
            throw new InvalidOperationException(
                "The current admin could not be identified.");
        }


        var modification =
            new Modification
            {
                ModificationId =
                    GetNextModificationId(),

                AdminId =
                    adminId.Value,

                DateModified =
                    DateTime.Today,

                TypeOfModification =
                    type,

                Discount =
                    discount
            };


        _context.Modifications.Add(
            modification);

        _context.SaveChanges();


        var modificationProduct =
            new ModificationProduct
            {
                ModificationId =
                    modification.ModificationId,

                ProductId =
                    productId
            };


        _context.ModificationProducts.Add(
            modificationProduct);

        _context.SaveChanges();
    }
}