using System.Text;
using System.Threading.RateLimiting;
using DoliMiddlewareApi.Exceptions;
using DoliMiddlewareApi.Services;
using DoliMiddlewareApi.Services.Auth;
using DoliMiddlewareApi.Services.Clients;
using DoliMiddlewareApi.Services.Notifications;
using DoliMiddlewareApi.Services.VeriFactu;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi;

// =========================================
// PROGRAM.CS - CONFIGURACIÓN Y DEPENDENCIAS
// =========================================

var builder = WebApplication.CreateBuilder(args);

if (string.IsNullOrEmpty(builder.Configuration["Jwt:Secret"]))
    throw new InvalidOperationException(
        "JWT Secret is required. Set 'Jwt__Secret' environment variable, 'Jwt:Secret' in appsettings, or use 'dotnet user-secrets set Jwt:Secret <value>'.");

// =========================================
// 1. CONFIGURACIÓN DE SERVICIOS ASP.NET CORE
// =========================================

// Controllers + JSON (camelCase + enums as strings)
builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
        options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
    });

// JWT Standard (ASP.NET auto-validation)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"] ?? "DoliMiddleware",
            ValidAudience = builder.Configuration["Jwt:Audience"] ?? "DoliClients",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"]!))
        };
    });

builder.Services.AddAuthorization();

// CORS: Permitir cualquier origen (demo)
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowVueApp", policy =>
    {
        policy.AllowAnyOrigin()
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});

// Swagger/OpenAPI
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "DoliMiddlewareApi",
        Version = "1.0",
        Description = "BFF API for Dolibarr ERP - Middleware between frontend and Dolibarr"
    });

    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Scheme = "bearer",
        BearerFormat = "JWT",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.Http,
        Description = "Enter your JWT Bearer token (obtained from POST /api/Auth/login)"
    });

    options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
    {
        [new OpenApiSecuritySchemeReference("Bearer", document)] = []
    });
});

// Health checks (verifica que el BFF responde)
builder.Services.AddHealthChecks();

// Rate limiting: proteger login contra brute force
builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
    {
        var path = context.Request.Path.Value;
        if (path != null && path.StartsWith("/api/Auth/login", StringComparison.OrdinalIgnoreCase))
        {
            return RateLimitPartition.GetSlidingWindowLimiter("login", _ => new SlidingWindowRateLimiterOptions
            {
                PermitLimit = 5,
                Window = TimeSpan.FromMinutes(1),
                SegmentsPerWindow = 2,
                QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
                QueueLimit = 0
            });
        }
        return RateLimitPartition.GetNoLimiter("default");
    });
});


// 2. DOLIBARR HTTP CLIENT
// =========================================
// HttpClient named para Dolibarr (configurado solo con BaseAddress)
// DolibarrApiClient se crea manual porque necesita HttpClient + TokenCacheService
//
// POR QUÉ NO usar AddHttpClient<TClient>() directamente?
// --------------------------------------------------------
// HttpClient es COMPARTIDO entre todos los usuarios
// Si configuramos el token ahí, el Usuario A usaría el token del Usuario B
// Solución: TokenCacheService obtiene el token del USUARIO ACTUAL (del JWT)
// factory.CreateClient("Dolibarr") devuelve el MISMO HttpClient reusado
// No se crea un cliente por request, los headers son por request (thread-safe)
// =========================================

builder.Services.AddHttpClient("Dolibarr", client =>
{
    var baseUrl = builder.Configuration["Dolibarr:ApiUrl"]!;
    if (!baseUrl.EndsWith('/')) baseUrl += '/';
    client.BaseAddress = new Uri(baseUrl);
});

builder.Services.AddHttpClient("VeriFactu", client =>
{
    var baseUrl = builder.Configuration["VeriFactu:ApiUrl"] ?? "http://localhost:6789/";
    if (!baseUrl.EndsWith('/')) baseUrl += '/';
    client.BaseAddress = new Uri(baseUrl);
    client.Timeout = TimeSpan.FromSeconds(30);
});

builder.Services.AddScoped<IDolibarrApiClient>(sp =>
{
    var factory = sp.GetRequiredService<IHttpClientFactory>();
    var client = factory.CreateClient("Dolibarr");
    var tokenCacheService = sp.GetRequiredService<DolibarrTokenCacheService>();
    var config = sp.GetRequiredService<IConfiguration>();
    return new DolibarrApiClient(client, tokenCacheService, config);
});

// =========================================
// 3. REGISTRO DE SERVICIOS (DEPENDENCIAS)
// =========================================

// Servicio de negocio (facturas)
builder.Services.AddScoped<InvoiceService>();

// Servicio de negocio (clientes)
builder.Services.AddScoped<ClientService>();

// Servicio de negocio (contactos)
builder.Services.AddScoped<ContactService>();

// Servicio de negocio (documentos)
builder.Services.AddScoped<DocumentService>();

// Servicio de negocio (setup/diccionarios)
builder.Services.AddScoped<SetupService>();

// Cliente de VeriFactu MidAPI
builder.Services.AddScoped<VeriFactuApiClient>(sp =>
{
    var factory = sp.GetRequiredService<IHttpClientFactory>();
    return new VeriFactuApiClient(factory.CreateClient("VeriFactu"));
});

// Servicio de negocio (banco)
builder.Services.AddScoped<BankService>();

// Servicio de facturas de proveedores
builder.Services.AddScoped<SupplierInvoiceService>();

// Servicio de aplicación (orquesta login + cache)
builder.Services.AddScoped<AuthApplicationService>();

// Servicio de autenticación con Dolibarr
builder.Services.AddScoped<DolibarrAuthService>();

// Servicio de cache de tokens de Dolibarr
builder.Services.AddScoped<DolibarrTokenCacheService>();

// Generador de JWT - Singleton porque es stateless
builder.Services.AddSingleton<JwtTokenProvider>();

// Cache en memoria para tokens (IMemoryCache)
builder.Services.AddMemoryCache();

// HttpContext accessor para acceder a User.Claims en servicios
builder.Services.AddHttpContextAccessor();

builder.Services.AddHttpClient("Webhook", c => c.Timeout = TimeSpan.FromSeconds(8));

// =========================================
// NOTIFICACIONES — Webhook (Teams / Slack)
// Configura Notifications:WebhookUrl en appsettings o variable de entorno
// Si no está configurado, las notificaciones se ignoran silenciosamente
// WebhookSettings es singleton para permitir actualización en tiempo de ejecución
// =========================================
builder.Services.AddSingleton<WebhookSettings>();
builder.Services.AddScoped<INotificationService, WebhookNotificationService>();

var app = builder.Build();

// =========================================
// 4. GLOBAL EXCEPTION HANDLER
// =========================================

app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        var exceptionHandler = context.Features.Get<IExceptionHandlerFeature>();
        var exception = exceptionHandler?.Error;
        var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
        var env = context.RequestServices.GetRequiredService<IWebHostEnvironment>();

        // Clasificar el tipo de error
        var (statusCode, title, detail, isExpectedError) = exception switch
        {
            NotFoundException notFound => (StatusCodes.Status404NotFound, "Not Found", notFound.Message, true),
            UnauthorizedException => (StatusCodes.Status401Unauthorized, "Unauthorized", "Invalid API credentials", true),
            ForbiddenException forbidden => (StatusCodes.Status403Forbidden, "Forbidden", forbidden.Message, true),
            BadRequestException badRequest => (StatusCodes.Status400BadRequest, "Bad Request", badRequest.Message, true),
            ApiException apiEx => (StatusCodes.Status500InternalServerError, "External Service Error",
                env.IsDevelopment() ? apiEx.Message : "The external service is temporarily unavailable", false),
            _ => (StatusCodes.Status500InternalServerError, "Internal Server Error",
                env.IsDevelopment()
                    ? exception?.Message ?? "An unexpected error occurred"
                    : "An unexpected error occurred. Please try again later.", false)
        };

        // Loguear siempre (crítico para debugging)
        if (isExpectedError)
        {
            logger.LogWarning(exception,
                "Expected error: {ExceptionType} | Path: {Path} | Message: {Message}",
                exception?.GetType().Name, context.Request.Path, exception?.Message);
        }
        else
        {
            logger.LogError(exception,
                "UNHANDLED EXCEPTION: {ExceptionType} | Path: {Path} | Message: {Message} | StackTrace: {StackTrace}",
                exception?.GetType().Name, context.Request.Path, exception?.Message, exception?.StackTrace);
        }

        // Construir respuesta ProblemDetails (RFC 7807)
        context.Response.StatusCode = statusCode;
        context.Response.ContentType = "application/problem+json";

        var problemDetails = new Microsoft.AspNetCore.Mvc.ProblemDetails
        {
            Status = statusCode,
            Title = title,
            Detail = detail,
            Instance = context.Request.Path
        };

        // En desarrollo añadir info extra para debugging
        if (env.IsDevelopment() && !isExpectedError)
        {
            problemDetails.Extensions["exceptionType"] = exception?.GetType().Name;
            problemDetails.Extensions["stackTrace"] = exception?.StackTrace;
            if (exception?.InnerException != null)
            {
                problemDetails.Extensions["innerException"] = exception.InnerException.Message;
                problemDetails.Extensions["innerStackTrace"] = exception.InnerException.StackTrace;
            }
        }

        await context.Response.WriteAsJsonAsync(problemDetails);
    });
});

// =========================================
// 5. PIPELINE ASP.NET CORE
// =========================================

app.UseSwagger();
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/swagger/v1/swagger.json", "DoliMiddlewareApi v1");
});

// HTTPS redirect solo si ForceHttps=true (nunca en Docker)
if (bool.TryParse(builder.Configuration["Dolibarr:ForceHttps"], out var forceHttps) && forceHttps)
    app.UseHttpsRedirection();

app.UseCors("AllowVueApp");

app.UseRateLimiter();

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

app.MapControllers();

app.MapHealthChecks("/health");

app.Run();
