using System.IO.Compression;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.AspNetCore.ResponseCompression;
using MudBlazor.Services;
using Serilog;
using FluentValidation;
using System.Threading.RateLimiting;
using TaxBaik.Application;
using TaxBaik.Application.Services;
using TaxBaik.Application.Seasonal;
using TaxBaik.Application.Utils;
using TaxBaik.Infrastructure;
using TaxBaik.Web.Services;
// Client (WASM) 서비스는 Client 프로젝트에서만 사용됨

var builder = WebApplication.CreateBuilder(args);
var isProduction = builder.Environment.IsProduction();

// HTTP 요청 헤더/쿠키 크기 제한 증가 (400 Bad Request 해결)
builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxRequestBodySize = 100 * 1024 * 1024; // 100MB
});

// Serilog 설정
builder.Host.UseSerilog((context, config) =>
{
    config
        .MinimumLevel.Information()
        .WriteTo.Console()
        .WriteTo.File(
            path: "logs/taxbaik-web-.log",
            rollingInterval: RollingInterval.Day,
            outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message:lj}{NewLine}{Exception}")
        .Enrich.FromLogContext()
        .Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName);

    var botToken = context.Configuration["Telegram:BotToken"];
    var systemChatId = context.Configuration["Telegram:SystemChatId"] ?? context.Configuration["Telegram:ChatId"];
    if (context.HostingEnvironment.IsProduction()
        && !string.IsNullOrEmpty(botToken)
        && !string.IsNullOrEmpty(systemChatId))
    {
        config.WriteTo.Sink(new TaxBaik.Web.Logging.TelegramSink(botToken, systemChatId), Serilog.Events.LogEventLevel.Error);
    }
});

builder.Services.AddAuthorization();
builder.Services.AddProblemDetails();
builder.Services.AddHealthChecks();
var dataProtectionKeyPath = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
    ".taxbaik",
    "data-protection-keys");
Directory.CreateDirectory(dataProtectionKeyPath);
builder.Services.AddDataProtection()
    .SetApplicationName("TaxBaik.Web")
    .PersistKeysToFileSystem(new DirectoryInfo(dataProtectionKeyPath));
builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    options.AddPolicy("client-logs", httpContext =>
    {
        var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
        return RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: $"client-logs:{ip}",
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 10,
                Window = TimeSpan.FromMinutes(1),
                QueueLimit = 0,
                AutoReplenishment = true
            });
    });
    options.AddPolicy("admin-login", httpContext =>
    {
        var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
        return RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: $"admin-login:{ip}",
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 5,
                Window = TimeSpan.FromMinutes(1),
                QueueLimit = 0,
                AutoReplenishment = true
            });
    });
});

// Razor Pages
builder.Services.AddRazorPages();

// Session & TempData (쿠키 저장소)
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(20);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
    options.Cookie.Name = "TaxBaik.SessionId";
    options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax;
});
builder.Services.AddDistributedMemoryCache();
// TempData는 기본적으로 쿠키 저장소 사용 (위 세션 설정 상속)

// 인증: 관리자 쿠키 + 포털 쿠키
var connectionString = builder.Configuration.GetConnectionString("Default")
    ?? throw new InvalidOperationException("Missing connection string");

var authenticationBuilder = builder.Services.AddAuthentication(opts =>
{
    opts.DefaultAuthenticateScheme = PortalAuthDefaults.Scheme;
    opts.DefaultChallengeScheme = PortalAuthDefaults.Scheme;
})
.AddCookie(PortalAuthDefaults.Scheme, opts =>
{
    opts.Cookie.Name = PortalAuthDefaults.CookieName;
    opts.Cookie.HttpOnly = true;
    opts.Cookie.SameSite = SameSiteMode.Lax;
    opts.Cookie.SecurePolicy = isProduction ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
    opts.LoginPath = "/portal/login";
    opts.AccessDeniedPath = "/portal/login";
    opts.SlidingExpiration = true;
    opts.ExpireTimeSpan = TimeSpan.FromDays(7);
})
.AddCookie(AdminAuthDefaults.Scheme, opts =>
{
    opts.Cookie.Name = AdminAuthDefaults.CookieName;
    opts.Cookie.HttpOnly = true;
    opts.Cookie.SameSite = SameSiteMode.Lax;
    opts.Cookie.SecurePolicy = isProduction ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
    opts.LoginPath = "/admin/login";
    opts.AccessDeniedPath = "/admin/login";
    opts.SlidingExpiration = true;
    opts.ExpireTimeSpan = TimeSpan.FromHours(12);
})
.AddCookie(PortalOAuthDefaults.ExternalScheme, opts =>
{
    opts.Cookie.Name = "TaxBaik.Portal.External";
    opts.Cookie.HttpOnly = true;
    opts.Cookie.SameSite = SameSiteMode.Lax;
    opts.Cookie.SecurePolicy = isProduction ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
});

builder.Services.AddAuthorization();

var googleClientId = builder.Configuration["Authentication:Google:ClientId"];
var googleClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];
if (!string.IsNullOrWhiteSpace(googleClientId) && !string.IsNullOrWhiteSpace(googleClientSecret))
{
    authenticationBuilder.AddGoogle(PortalOAuthDefaults.GoogleScheme, opts =>
    {
        opts.SignInScheme = PortalOAuthDefaults.ExternalScheme;
        opts.ClientId = googleClientId;
        opts.ClientSecret = googleClientSecret;
        opts.CallbackPath = "/portal/signin-google";
    });
}

var naverClientId = builder.Configuration["Authentication:Naver:ClientId"];
var naverClientSecret = builder.Configuration["Authentication:Naver:ClientSecret"];
if (!string.IsNullOrWhiteSpace(naverClientId) && !string.IsNullOrWhiteSpace(naverClientSecret))
{
    authenticationBuilder.AddOAuth(PortalOAuthDefaults.NaverScheme, opts =>
    {
        opts.SignInScheme = PortalOAuthDefaults.ExternalScheme;
        opts.ClientId = naverClientId;
        opts.ClientSecret = naverClientSecret;
        opts.CallbackPath = "/portal/signin-naver";
        opts.AuthorizationEndpoint = "https://nid.naver.com/oauth2.0/authorize";
        opts.TokenEndpoint = "https://nid.naver.com/oauth2.0/token";
        opts.UserInformationEndpoint = "https://openapi.naver.com/v1/nid/me";
        opts.SaveTokens = true;
        opts.Events = new OAuthEvents
        {
            OnCreatingTicket = async context =>
            {
                var request = new HttpRequestMessage(HttpMethod.Get, opts.UserInformationEndpoint);
                request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", context.AccessToken);
                var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
                response.EnsureSuccessStatusCode();
                using var payload = System.Text.Json.JsonDocument.Parse(await response.Content.ReadAsStringAsync(context.HttpContext.RequestAborted));
                var responseRoot = payload.RootElement.GetProperty("response");
                context.Identity?.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.NameIdentifier, responseRoot.GetProperty("id").GetString() ?? ""));
                context.Identity?.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, responseRoot.GetProperty("name").GetString() ?? ""));
                context.Identity?.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Email, responseRoot.GetProperty("email").GetString() ?? ""));
            }
        };
    });
}

var kakaoClientId = builder.Configuration["Authentication:Kakao:ClientId"];
var kakaoClientSecret = builder.Configuration["Authentication:Kakao:ClientSecret"];
if (!string.IsNullOrWhiteSpace(kakaoClientId) && !string.IsNullOrWhiteSpace(kakaoClientSecret))
{
    authenticationBuilder.AddOAuth(PortalOAuthDefaults.KakaoScheme, opts =>
    {
        opts.SignInScheme = PortalOAuthDefaults.ExternalScheme;
        opts.ClientId = kakaoClientId;
        opts.ClientSecret = kakaoClientSecret;
        opts.CallbackPath = "/portal/signin-kakao";
        opts.AuthorizationEndpoint = "https://kauth.kakao.com/oauth/authorize";
        opts.TokenEndpoint = "https://kauth.kakao.com/oauth/token";
        opts.UserInformationEndpoint = "https://kapi.kakao.com/v2/user/me";
        opts.SaveTokens = true;
        opts.Events = new OAuthEvents
        {
            OnCreatingTicket = async context =>
            {
                var request = new HttpRequestMessage(HttpMethod.Get, opts.UserInformationEndpoint);
                request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", context.AccessToken);
                var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
                response.EnsureSuccessStatusCode();
                using var payload = System.Text.Json.JsonDocument.Parse(await response.Content.ReadAsStringAsync(context.HttpContext.RequestAborted));
                var kakaoAccount = payload.RootElement.GetProperty("kakao_account");
                var profile = kakaoAccount.GetProperty("profile");
                context.Identity?.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.NameIdentifier, payload.RootElement.GetProperty("id").GetInt64().ToString()));
                context.Identity?.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, profile.GetProperty("nickname").GetString() ?? ""));
                if (kakaoAccount.TryGetProperty("email", out var emailProp))
                    context.Identity?.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Email, emailProp.GetString() ?? ""));
            }
        };
    });
}

// Telegram Notification
builder.Services.AddHttpClient<ITelegramNotificationService, TelegramNotificationService>();

// UI & 캐시 (MudBlazor Theme Customization)
builder.Services.AddMudServices(config =>
{
    config.SnackbarConfiguration.HideTransitionDuration = 400;
    config.SnackbarConfiguration.ShowTransitionDuration = 300;
    config.PopoverOptions.ThrowOnDuplicateProvider = false;
});
builder.Services.AddMemoryCache();
builder.Services.AddResponseCompression(opts =>
{
    opts.Providers.Add<GzipCompressionProvider>();
});
builder.Services.AddHostedService<TelegramReportBackgroundService>();
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<PortalAuthService>();
builder.Services.AddScoped<AuthService>();

builder.Services.Configure<PortalAuthOptions>(builder.Configuration.GetSection("Authentication"));

// 한글 포함 다국어 문자를 유니코드 엔티티로 변환하지 않도록 설정
builder.Services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All));

builder.Services.AddInfrastructure();
builder.Services.AddApplication();
builder.Services.AddValidatorsFromAssemblyContaining<TaxBaik.Application.DTOs.CreateBlogPostDtoValidator>();
builder.Services.AddScoped<IInquiryNotificationService, TelegramInquiryNotificationService>();
builder.Services.AddScoped<TaxBaik.Web.Services.SitemapValidationService>();

// Register version info
var versionInfo = new VersionInfo();
var assembly = typeof(Program).Assembly;
var assemblyVersion = assembly.GetName().Version?.ToString() ?? "unknown";
var informationalVersion = assembly.GetCustomAttributes(typeof(System.Reflection.AssemblyInformationalVersionAttribute), false)
    .OfType<System.Reflection.AssemblyInformationalVersionAttribute>()
    .FirstOrDefault()?.InformationalVersion;

versionInfo.Version = informationalVersion ?? assemblyVersion;
versionInfo.Built = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd HH:mm:ss 'UTC'");
var versionJsonPath = Path.Combine(AppContext.BaseDirectory, "wwwroot", "version.json");
if (File.Exists(versionJsonPath))
{
    try
    {
        var json = System.Text.Json.JsonDocument.Parse(File.ReadAllText(versionJsonPath));
        var root = json.RootElement;
        if (root.TryGetProperty("version", out var versionProp))
            versionInfo.Version = versionProp.GetString() ?? "unknown";
        if (root.TryGetProperty("built", out var builtProp))
            versionInfo.Built = builtProp.GetString() ?? "unknown";
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Warning: Failed to parse version.json: {ex.Message}");
    }
}
builder.Services.AddSingleton(versionInfo);

var app = builder.Build();

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

app.Use(async (context, next) =>
{
    var path = context.Request.Path.Value ?? string.Empty;
    if (path.Equals("/favicon.ico", StringComparison.OrdinalIgnoreCase) ||
        path.Equals("/favicon.ico", StringComparison.OrdinalIgnoreCase))
    {
        context.Response.ContentType = "image/svg+xml";
        await context.Response.SendFileAsync(Path.Combine(app.Environment.WebRootPath ?? "wwwroot", "favicon.svg"));
        return;
    }

    await next();
});

// Run migrations on startup (non-blocking for development)
try
{
    using (var scope = app.Services.CreateScope())
    {
        var connectionFactory = scope.ServiceProvider.GetRequiredService<TaxBaik.Domain.Interfaces.IDbConnectionFactory>();
        var migrationRunner = new TaxBaik.Infrastructure.Data.MigrationRunner(connectionString, connectionFactory);
        await migrationRunner.RunAsync();
    }
}
catch (Exception ex)
{
    if (!app.Environment.IsDevelopment())
        throw;

    Console.WriteLine($"Migration warning (development only): {ex.Message}");
}

// PathBase는 사용하지 않음

app.UseResponseCompression();

// 정적 파일 제공
app.UseStaticFiles();
app.UseStaticFiles("/portal");

app.UseSession();  // TempData 쿠키 저장소
app.UseRouting();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.Use(async (context, next) =>
{
    context.Response.Headers["X-Content-Type-Options"] = "nosniff";
    context.Response.Headers["X-Frame-Options"] = "DENY";
    context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
    context.Response.Headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()";
    context.Response.Headers["X-Permitted-Cross-Domain-Policies"] = "none";
    if (!context.Response.Headers.ContainsKey("Content-Security-Policy"))
    {
        context.Response.Headers["Content-Security-Policy"] = "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://www.googletagmanager.com; connect-src 'self' https:; frame-ancestors 'none'; base-uri 'self'; form-action 'self';";
    }

    await next();
});

// Razor Pages + 정적 자산 매핑
app.MapHealthChecks("/healthz");
app.MapRazorPages();  // Sitemap.cshtml, Rss.cshtml, Feed.cshtml
app.MapStaticAssets();

// 애플리케이션 시작/종료 로깅
try
{
    Log.Information("애플리케이션 시작: {Environment}", app.Environment.EnvironmentName);
    await app.RunAsync();
}
catch (Exception ex)
{
    Log.Fatal(ex, "애플리케이션 강종");
    // NOTE: RunAsync() 후 app.Services는 dispose됨.
    // Telegram 알림은 별도 모니터링 백그라운드 작업으로 처리
    throw;
}
finally
{
    Log.Information("애플리케이션 종료");
    Log.CloseAndFlush();
}

