using FluentValidation;

using Forwarder;
using Forwarder.Data;
using Forwarder.Exceptions;
using Forwarder.Models;
using Forwarder.Modules.Ademco;
using Forwarder.Repositories;
using Forwarder.Repositories.Implementations;
using Forwarder.Services;
using Forwarder.Services.Implementations;

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

using Monitoring.Common;

using Polly;

using Serilog;
using Serilog.Enrichers.CallerInfo;

using TelegramSender;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddControllers();
builder.AddServiceDefaults();

builder.Services.AddOpenApi();

Log.Logger = new LoggerConfiguration()
	.Enrich.FromLogContext()
	.Enrich.WithCallerInfo(
		includeFileInfo: true,
		assemblyPrefix: "forwarder_")
	.MinimumLevel.Information()
	.WriteTo.Console()
	.CreateLogger();
builder.Host.UseSerilog();

builder.Logging.SetMinimumLevel(LogLevel.Debug);

builder.Services.AddCors(options =>
{
	options.AddDefaultPolicy(builder =>
	{
		_ = builder.AllowAnyOrigin()
			   .AllowAnyHeader()
			   .AllowAnyMethod();
	});
});

builder.Services.AddControllers();

if (EnvironmentVariables.Has("DATABASE_ASPIRE_NAME"))
{
	var connectionName = EnvironmentVariables.Get("DATABASE_ASPIRE_NAME");
	builder.AddNpgsqlDbContext<DataContext>(connectionName: connectionName);
	builder.Services.AddScoped<IDataContext, DataContext>(serviceProvider => serviceProvider.GetRequiredService<DataContext>());
}
else
{
	var connectionString = EnvironmentVariables.Get("DATABASE_CONNECTION_STRING");
	builder.Services.AddDbContext<IDataContext, DataContext>(options =>
	{
		_ = options.UseNpgsql(connectionString)
			.UseLoggerFactory(LoggerFactory.Create(x => x.SetMinimumLevel(LogLevel.Warning)));
		_ = options.EnableSensitiveDataLogging(false);
	});
}

builder.Services.AddValidatorsFromAssemblyContaining<Program>();

var pollyPolicy = GetRetryPolicy();

builder.Services.AddHttpClient(HttpClients.Api, (serviceProvider, client) =>
{
	var options = serviceProvider.GetRequiredService<IOptions<ForwarderOptions>>().Value;
	client.BaseAddress = new Uri(options.ApiUrl);
	client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory");
})
	.SetHandlerLifetime(TimeSpan.FromMinutes(5))
	.AddPolicyHandler(pollyPolicy);

builder.Services.Configure<GeoRitmConnectionOptions>(builder.Configuration);
builder.Services.AddHttpClient(HttpClients.GeoRitm, (sp, client) =>
{
	var options = sp.GetRequiredService<IOptions<GeoRitmConnectionOptions>>().Value;
	client.BaseAddress = new Uri(options.Url);
	client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory");
	var scope = sp.CreateScope();
	var basicAuthService = scope.ServiceProvider.GetRequiredService<IBasicAuthService>();
	client.DefaultRequestHeaders.Add(
		"Authorization",
		basicAuthService.GetBasicAuth(options.Username, options.Password));
})
	.SetHandlerLifetime(TimeSpan.FromMinutes(5))
	.AddPolicyHandler(pollyPolicy);

builder.Services.Configure<ForwarderOptions>(builder.Configuration);

builder.Services.AddHostedService<TcpWatcher>();
builder.Services.AddHostedService<UdpWatcher>();
builder.Services.AddHostedService<SendJob>();
// builder.Services.AddHostedService<OnlineStatusJob>();

// Репозитории
builder.Services.AddScoped<IMessageRepository, MessageRepository>();
builder.Services.AddScoped<IAssetMapRepository, AssetMapRepository>();
builder.Services.AddScoped<ICommonRepository, CommonRepository>();

// Отправители
builder.Services.AddScoped<ISendService, SendService>();
builder.Services.AddScoped<IGeoRitmClient, GeoRitmClient>();
builder.Services.AddScoped<IBasicAuthService, BasicAuthService>();

// Протоколы
builder.Services.AddScoped<IMessageConverterService, MessageConverterService>();

// Уведомления для пользователей
builder.Services.Configure<TelegramSendOptions>(builder.Configuration);
if (EnvironmentVariables.Has("TELEGRAM_BOT_TOKEN"))
{
	builder.Services.AddTelegramSender(
		GetEnvironmentVariable("TELEGRAM_BOT_TOKEN"),
		int.Parse(GetEnvironmentVariable("TELEGRAM_BATCH_SIZE")),
		long.Parse(GetEnvironmentVariable("TELEGRAM_CHAT_ID")));
}
else
{
	builder.Services.AddTelegramSender(string.Empty, default, default);
}
// Ademco
builder.AddAdemcoModule();

var app = builder.Build();

app.UseSerilogRequestLogging();

if (app.Environment.IsDevelopment())
{
	_ = app.MapOpenApi();
	//_ = app.MapScalarApiReference()
	//	.WithName("Forwarder API");
}

app.UseRouting();
app.UseCors();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.MapDefaultEndpoints();

await ApplyMigrations(app);

await app.RunAsync();

static async Task ApplyMigrations(WebApplication app)
{
	var context = app.Services.CreateScope().ServiceProvider.GetRequiredService<DataContext>();
	await context.Database.MigrateAsync();
}

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
	return Policy
		.Handle<HttpRequestException>()
		.WaitAndRetryAsync(
			retryCount: 10,
			sleepDurationProvider: _ => TimeSpan.FromSeconds(1),
			onRetry: (exception, timeSpan, attempt, context) =>
			{
				Log.Warning("Request failed #{Attempt}. Waiting {TimeSpan} before next retry. Retry due to: {Exception}.", attempt, timeSpan, exception.Message);
			})
		.AsAsyncPolicy<HttpResponseMessage>();
}

static string GetEnvironmentVariable(string name)
{
	return Environment.GetEnvironmentVariable(name)
		?? throw new EnvironmentVariableNotFoundException(name);
}