using AmiReel.Models;
using AmiReel.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace AmiReel.Tests.Services;

[TestClass]
public class ShortsPipelineTests
{
    private readonly List<string> _tempDirectories = [];

    [TestCleanup]
    public void Cleanup()
    {
        foreach (string directory in _tempDirectories)
        {
            try { Directory.Delete(directory, true); } catch { }
        }
    }

    private string CreateTempDirectory()
    {
        string directory = Path.Combine(Path.GetTempPath(), "AmiReelTests", Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(directory);
        _tempDirectories.Add(directory);
        return directory;
    }

    private static ShortSettings ValidSettings(string input, string font, ShortStyle style = ShortStyle.Crop, string background = "") => new()
    {
        InputFile = input,
        OutputPath = Path.Combine(Path.GetDirectoryName(input) ?? "", "out.mp4"),
        Title = "Test Short",
        Hook = "THIS RAN ON AN AMIGA",
        Website = "AMIGADB.NET",
        Start = "00:00:00",
        DurationSeconds = 30,
        Style = style,
        BackgroundImage = background,
        FontFile = font,
        FfmpegPath = "",
        FfprobePath = "",
    };

    [TestMethod]
    public void EscapeFfmpegPath_WithBackslashesColonsAndQuotes_EscapesAllThree()
    {
        // Act
        string result = ShortsPipeline.EscapeFfmpegPath(@"C:\Fonts\font's.ttf");

        // Assert
        Assert.AreEqual(@"C\:\\Fonts\\font\'s.ttf", result);
    }

    [TestMethod]
    public void EscapeFfmpegPath_EscapesBackslashesBeforeIntroducingNewOnes()
    {
        // Act: if colon/quote escaping ran before backslash escaping, the backslashes
        // they introduce would themselves get doubled, which must not happen.
        string result = ShortsPipeline.EscapeFfmpegPath("C:\\a");

        // Assert
        Assert.AreEqual("C\\:\\\\a", result);
    }

    [TestMethod]
    public void BuildMeta_WithAllFieldsPopulated_JoinsWithMiddleDot()
    {
        // Arrange
        ShortSettings settings = ValidSettings("in.mp4", "font.ttf") with { Group = "Pirates", Year = "1991", Type = "Cracktro" };

        // Act
        string meta = ShortsPipeline.BuildMeta(settings);

        // Assert
        Assert.AreEqual("Pirates · 1991 · Cracktro", meta);
    }

    [TestMethod]
    public void BuildMeta_WithSomeFieldsBlank_SkipsBlankFields()
    {
        // Arrange
        ShortSettings settings = ValidSettings("in.mp4", "font.ttf") with { Group = "Pirates", Year = "", Type = "  " };

        // Act
        string meta = ShortsPipeline.BuildMeta(settings);

        // Assert
        Assert.AreEqual("Pirates", meta);
    }

    [TestMethod]
    public void BuildMeta_WithAllFieldsBlank_ReturnsEmptyString()
    {
        // Arrange
        ShortSettings settings = ValidSettings("in.mp4", "font.ttf");

        // Act
        string meta = ShortsPipeline.BuildMeta(settings);

        // Assert
        Assert.AreEqual("", meta);
    }

    [TestMethod]
    public void Validate_WithMissingInputFile_ThrowsFileNotFoundException()
    {
        // Arrange
        string directory = CreateTempDirectory();
        ShortSettings settings = ValidSettings(Path.Combine(directory, "missing.mp4"), "font.ttf");

        // Act & Assert
        Assert.ThrowsExactly<FileNotFoundException>(() => ShortsPipeline.Validate(settings));
    }

    [TestMethod]
    public void Validate_WithBlankTitle_ThrowsArgumentException()
    {
        // Arrange
        string directory = CreateTempDirectory();
        string input = Path.Combine(directory, "clip.mp4");
        File.WriteAllText(input, "data");
        string font = Path.Combine(directory, "font.ttf");
        File.WriteAllText(font, "data");
        ShortSettings settings = ValidSettings(input, font) with { Title = "" };

        // Act & Assert
        Assert.ThrowsExactly<ArgumentException>(() => ShortsPipeline.Validate(settings));
    }

    [TestMethod]
    public void Validate_WithZeroDuration_ThrowsArgumentException()
    {
        // Arrange
        string directory = CreateTempDirectory();
        string input = Path.Combine(directory, "clip.mp4");
        File.WriteAllText(input, "data");
        string font = Path.Combine(directory, "font.ttf");
        File.WriteAllText(font, "data");
        ShortSettings settings = ValidSettings(input, font) with { DurationSeconds = 0 };

        // Act & Assert
        Assert.ThrowsExactly<ArgumentException>(() => ShortsPipeline.Validate(settings));
    }

    [TestMethod]
    public void Validate_WithMissingFontFile_ThrowsFileNotFoundException()
    {
        // Arrange
        string directory = CreateTempDirectory();
        string input = Path.Combine(directory, "clip.mp4");
        File.WriteAllText(input, "data");
        ShortSettings settings = ValidSettings(input, Path.Combine(directory, "missing-font.ttf"));

        // Act & Assert
        Assert.ThrowsExactly<FileNotFoundException>(() => ShortsPipeline.Validate(settings));
    }

    [TestMethod]
    public void Validate_BrandStyleWithoutBackgroundImage_ThrowsFileNotFoundException()
    {
        // Arrange
        string directory = CreateTempDirectory();
        string input = Path.Combine(directory, "clip.mp4");
        File.WriteAllText(input, "data");
        string font = Path.Combine(directory, "font.ttf");
        File.WriteAllText(font, "data");
        ShortSettings settings = ValidSettings(input, font, ShortStyle.Brand);

        // Act & Assert
        Assert.ThrowsExactly<FileNotFoundException>(() => ShortsPipeline.Validate(settings));
    }

    [TestMethod]
    public void Validate_NonBrandStyleWithoutBackgroundImage_DoesNotThrow()
    {
        // Arrange
        string directory = CreateTempDirectory();
        string input = Path.Combine(directory, "clip.mp4");
        File.WriteAllText(input, "data");
        string font = Path.Combine(directory, "font.ttf");
        File.WriteAllText(font, "data");
        ShortSettings settings = ValidSettings(input, font, ShortStyle.Crop);

        // Act & Assert (no exception)
        ShortsPipeline.Validate(settings);
    }

    [TestMethod]
    public void GetBaseFilter_ForEveryStyle_ReturnsAFilterEndingInBaseTag()
    {
        foreach (ShortStyle style in Enum.GetValues<ShortStyle>())
        {
            // Act
            string filter = ShortsPipeline.GetBaseFilter(style);

            // Assert
            Assert.IsFalse(string.IsNullOrWhiteSpace(filter), $"{style} produced an empty filter.");
            StringAssert.Contains(filter, "[base]", $"{style} filter does not label an output as [base].");
        }
    }

    [TestMethod]
    public void BuildFilterComplex_IncludesAllFourTextOverlaysAndTheirTimingGates()
    {
        // Act
        string result = ShortsPipeline.BuildFilterComplex(
            "[0:v]copy[base]",
            fontEsc: "C\\:\\\\font.ttf",
            hookEsc: "hook.txt", titleEsc: "title.txt", metaEsc: "meta.txt", websiteEsc: "website.txt",
            hookEnd: 2.7, infoStart: 23, ctaStart: 26.5);

        // Assert
        StringAssert.Contains(result, "textfile='hook.txt'");
        StringAssert.Contains(result, "textfile='title.txt'");
        StringAssert.Contains(result, "textfile='meta.txt'");
        StringAssert.Contains(result, "textfile='website.txt'");
        StringAssert.Contains(result, "enable='between(t,0,2.7)'");
        StringAssert.Contains(result, "enable='gte(t,23)'");
        StringAssert.Contains(result, "enable='gte(t,26.5)'");
        StringAssert.Contains(result, "[vout]");
    }
}
