using Autofac;
using Insight.Database;
using LightBDD.Framework;
using LightBDD.Framework.Scenarios;
using LightBDD.MsTest3;
using PlanTempus.Components;
using PlanTempus.Components.Accounts.ConfirmEmail;
using PlanTempus.Components.Accounts.Create;
using PlanTempus.Core.Database;
using PlanTempus.Core.Outbox;
using Shouldly;

namespace PlanTempus.X.BDD.FeatureFixtures;

[TestClass]
[FeatureDescription(@"As a registered user
I want to confirm my email
So I can activate my account")]
public partial class EmailConfirmationSpecs : BddTestFixture
{
	protected string _currentEmail;
	protected string _securityStamp;
	protected bool _emailConfirmed;
	protected string _errorMessage;
	protected bool _outboxEntryCreated;

	public async Task Given_an_account_exists_with_unconfirmed_email(string email)
	{
		_currentEmail = email;

		var command = new CreateAccountCommand
		{
			CorrelationId = Guid.NewGuid(),
			Email = email,
			Password = "TestPassword123!"
		};

		await CommandHandler.Handle(command);

		// Hent security_stamp fra database til brug i confirmation
		var db = Container.Resolve<IDatabaseOperations>();
		using var scope = db.CreateScope(nameof(EmailConfirmationSpecs));

		var result = await scope.Connection.QuerySqlAsync<AccountDto>(
			"SELECT email_confirmed, security_stamp FROM system.accounts WHERE email = @Email",
			new { Email = email });

		result.Count.ShouldBe(1);
		result[0].EmailConfirmed.ShouldBeFalse();
		_securityStamp = result[0].SecurityStamp;
	}

	public async Task And_a_verification_email_is_queued_in_outbox()
	{
		var db = Container.Resolve<IDatabaseOperations>();
		using var scope = db.CreateScope(nameof(EmailConfirmationSpecs));

		var result = await scope.Connection.QuerySqlAsync<OutboxDto>(
			@"SELECT type, payload FROM system.outbox
			  WHERE type = @Type AND payload->>'Email' = @Email",
			new { Type = OutboxMessageTypes.VerificationEmail, Email = _currentEmail });

		result.Count.ShouldBeGreaterThan(0);
		_outboxEntryCreated = true;
	}

	public async Task And_the_outbox_message_is_processed()
	{
		// Vent på at OutboxListener når at behandle beskeden
		await Task.Delay(1000);

		var db = Container.Resolve<IDatabaseOperations>();
		using var scope = db.CreateScope(nameof(EmailConfirmationSpecs));

		var result = await scope.Connection.QuerySqlAsync<OutboxDto>(
			@"SELECT status FROM system.outbox
			  WHERE type = @Type AND payload->>'Email' = @Email",
			new { Type = OutboxMessageTypes.VerificationEmail, Email = _currentEmail });

		result.Count.ShouldBeGreaterThan(0);
		// Status skal være 'sent' eller 'failed' - begge indikerer at beskeden blev behandlet
		result[0].Status.ShouldBeOneOf("sent", "failed");
	}

	public async Task When_I_confirm_email_with_valid_token()
	{
		var command = new ConfirmEmailCommand
		{
			CorrelationId = Guid.NewGuid(),
			Email = _currentEmail,
			Token = _securityStamp
		};

		await CommandHandler.Handle(command);
	}

	public async Task When_I_confirm_email_with_invalid_token()
	{
		try
		{
			var command = new ConfirmEmailCommand
			{
				CorrelationId = Guid.NewGuid(),
				Email = _currentEmail ?? "unknown@example.com",
				Token = "invalid-token"
			};

			await CommandHandler.Handle(command);
		}
		catch (InvalidTokenException ex)
		{
			_errorMessage = ex.Message;
		}
	}

	public async Task Then_the_accounts_email_should_be_confirmed()
	{
		var db = Container.Resolve<IDatabaseOperations>();
		using var scope = db.CreateScope(nameof(EmailConfirmationSpecs));

		var result = await scope.Connection.QuerySqlAsync<AccountDto>(
			"SELECT email_confirmed FROM system.accounts WHERE email = @Email",
			new { Email = _currentEmail });

		result.Count.ShouldBe(1);
		result[0].EmailConfirmed.ShouldBeTrue();
	}

	public async Task Then_I_should_see_an_error_message(string expectedMessage)
	{
		_errorMessage.ShouldNotBeNull();
		_errorMessage.ShouldContain(expectedMessage);
	}

	public async Task And_the_email_remains_unconfirmed()
	{
		if (_currentEmail == null) return;

		var db = Container.Resolve<IDatabaseOperations>();
		using var scope = db.CreateScope(nameof(EmailConfirmationSpecs));

		var result = await scope.Connection.QuerySqlAsync<AccountDto>(
			"SELECT email_confirmed FROM system.accounts WHERE email = @Email",
			new { Email = _currentEmail });

		if (result.Count > 0)
		{
			result[0].EmailConfirmed.ShouldBeFalse();
		}
	}

	private class AccountDto
	{
		public bool EmailConfirmed { get; set; }
		public string SecurityStamp { get; set; }
	}

	private class OutboxDto
	{
		public string Type { get; set; }
		public string Payload { get; set; }
		public string Status { get; set; }
	}
}
