unit Test.Myc.Ast.RTL.DateTime;

interface

uses
    DUnitX.TestFramework,
    System.SysUtils,
    System.DateUtils,
    System.Math,
    Myc.Ast.Environment,
    Myc.Ast.Script,
    Myc.Data.Value,
    Myc.Data.Scalar;

type
    [TestFixture]
    [IgnoreMemoryLeaks]
    TTestRtlDateTime = class
    private
        FEnv: TAstEnvironment;
    public
        [Setup]
        procedure Setup;
        [Teardown]
        procedure Teardown;

        [Test]
        procedure Test_Constructor_FixedDate;

        [Test]
        procedure Test_Constructor_Now;

        [Test]
        [TestCase('Later', '2025,1,1,2024,1,1,True')]
        [TestCase('Earlier', '2020,1,1,2021,1,1,False')]
        procedure Test_GreaterThan(Y1, M1, D1, Y2, M2, D2: Integer; ExpectTrue: Boolean);

        [Test]
        procedure Test_Equality;

        [Test]
        procedure Test_DateTime_Is_Truthy;

        [Test]
        procedure Test_Round_DateTime;
    end;

implementation

procedure TTestRtlDateTime.Setup;
begin
    // Creates a fresh environment with all RTL functions registered
    // (including Date, Now, True, False)
    FEnv := TAstEnvironment.Construct(nil);
end;

procedure TTestRtlDateTime.Teardown;
begin
    // Environment is a record (interface wrapper), automatic cleanup
end;

procedure TTestRtlDateTime.Test_Constructor_FixedDate;
var
    Res: TDataValue;
    Expected: TDateTime;
begin
    // Act
    Res := FEnv.Run(TAstScript.Parse('(Date 2025 11 24)'));

    // Assert
    Assert.AreEqual(TDataValueKind.vkScalar, Res.Kind, 'Result should be scalar');
    Assert.AreEqual(TScalar.TKind.DateTime, Res.AsScalar.Kind, 'Scalar kind should be DateTime');

    Expected := EncodeDate(2025, 11, 24);
    Assert.AreEqual(Expected, Res.AsScalar.Value.AsDouble, 0.0001, 'Date value mismatch');
end;

procedure TTestRtlDateTime.Test_Constructor_Now;
var
    Res: TDataValue;
    Before, After: TDateTime;
begin
    Before := System.SysUtils.Now;
    // Allow small window for execution time
    Sleep(1);

    // Act
    Res := FEnv.Run(TAstScript.Parse('(Now)'));

    After := System.SysUtils.Now;

    // Assert
    Assert.AreEqual(TScalar.TKind.DateTime, Res.AsScalar.Kind);

    // Check if result is within the time window
    Assert.IsTrue(Res.AsScalar.Value.AsDouble >= Before);
    Assert.IsTrue(Res.AsScalar.Value.AsDouble <= After);
end;

procedure TTestRtlDateTime.Test_GreaterThan(Y1, M1, D1, Y2, M2, D2: Integer; ExpectTrue: Boolean);
var
    Script: string;
    Res: TDataValue;
begin
    // (> (Date y1 m1 d1) (Date y2 m2 d2))
    Script := Format('(> (Date %d %d %d) (Date %d %d %d))', [Y1, M1, D1, Y2, M2, D2]);

    Res := FEnv.Run(TAstScript.Parse(Script));

    Assert.AreEqual(TScalar.TKind.Boolean, Res.AsScalar.Kind);

    if ExpectTrue then
        Assert.AreEqual(Int64(1), Res.AsScalar.Value.AsInt64, 'Expected True')
    else
        Assert.AreEqual(Int64(0), Res.AsScalar.Value.AsInt64, 'Expected False');
end;

procedure TTestRtlDateTime.Test_Equality;
var
    Res: TDataValue;
begin
    // (= (Date 2023 10 10) (Date 2023 10 10))
    Res := FEnv.Run(TAstScript.Parse('(= (Date 2023 10 10) (Date 2023 10 10))'));

    Assert.AreEqual(TScalar.TKind.Boolean, Res.AsScalar.Kind);
    Assert.AreEqual(Int64(1), Res.AsScalar.Value.AsInt64);
end;

procedure TTestRtlDateTime.Test_DateTime_Is_Truthy;
var
    Res: TDataValue;
begin
    // (if (Date 2023 1 1) "yes" "no")
    // Checks if IsTruthy correctly handles TKind.DateTime
    Res := FEnv.Run(TAstScript.Parse('(if (Date 2023 1 1) "yes" "no")'));

    Assert.AreEqual(TDataValueKind.vkText, Res.Kind);
    Assert.AreEqual('yes', Res.AsText);
end;

procedure TTestRtlDateTime.Test_Round_DateTime;
var
    Res: TDataValue;
    InputDate: TDateTime;
    Expected: Int64;
begin
    // Ensure RTL Round handles DateTime as float
    // (Round (Date 2023 11 24))
    // Date creates a TDateTime (Double) with 0.0 time fraction, so Round should return the truncation.

    InputDate := EncodeDate(2023, 11, 24);
    Expected := System.Round(InputDate);

    Res := FEnv.Run(TAstScript.Parse('(Round (Date 2023 11 24))'));

    Assert.AreEqual(TScalar.TKind.Ordinal, Res.AsScalar.Kind);
    Assert.AreEqual(Expected, Res.AsScalar.Value.AsInt64);
end;

end.
