using System.IO.Compression;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.IdentityModel.Tokens;
using MudBlazor.Services;
using Serilog;
using System.Threading.RateLimiting;
using TaxBaik.Application;
using TaxBaik.Application.Services;
using TaxBaik.Application.Utils;
using TaxBaik.Infrastructure;
using TaxBaik.Web.Services;
using TaxBaik.Web.Services.AdminClients;

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-.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 (!string.IsNullOrEmpty(botToken) && !string.IsNullOrEmpty(systemChatId))
    {
        config.WriteTo.Sink(new TaxBaik.Web.Logging.TelegramSink(botToken, systemChatId), Serilog.Events.LogEventLevel.Error);
    }
});

// Controllers + FastEndpoints (API-First)
builder.Services.AddControllers();
builder.Services.AddFastEndpoints();
builder.Services.AddProblemDetails();
builder.Services.AddHealthChecks();
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
            });
    });
});

// Razor Pages + Blazor WebAssembly 통합
builder.Services.AddRazorPages();
builder.Services.AddRazorComponents()
    .AddInteractiveWebAssemblyComponents();
builder.Services.Configure<Microsoft.AspNetCore.Components.Server.CircuitOptions>(options =>
{
    options.DetailedErrors = true;
});

// Session & TempData (쿠키 저장소)
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(20);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
    options.Cookie.Name = "TaxBaik.SessionId";
});
builder.Services.AddDistributedMemoryCache();
// TempData는 기본적으로 쿠키 저장소 사용 (여기서 명시적 설정)

// JWT 인증
var connectionString = builder.Configuration.GetConnectionString("Default")
    ?? throw new InvalidOperationException("Missing connection string");
var jwtKey = builder.Configuration["Jwt:SecretKey"] ?? throw new InvalidOperationException("Missing JWT SecretKey");
if (isProduction && jwtKey.Contains("dev-secret", StringComparison.OrdinalIgnoreCase))
    throw new InvalidOperationException("Production JWT SecretKey must not use the development default.");
var key = Encoding.ASCII.GetBytes(jwtKey);

var authenticationBuilder = builder.Services.AddAuthentication(opts =>
{
    opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opts =>
{
    opts.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        ValidateIssuer = true,
        ValidIssuer = "taxbaik-admin",
        ValidateAudience = true,
        ValidAudience = "taxbaik-admin-client",
        ValidateLifetime = true,
        ClockSkew = TimeSpan.FromMinutes(1)
    };
})
.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 = "/taxbaik/portal/login";
    opts.AccessDeniedPath = "/taxbaik/portal/login";
    opts.SlidingExpiration = true;
    opts.ExpireTimeSpan = TimeSpan.FromDays(7);
})
.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;
});

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 = "/taxbaik/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 = "/taxbaik/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 = "/taxbaik/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() ?? ""));
            }
        };
    });
}

// Blazor 인증
builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<CustomAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(sp => sp.GetRequiredService<CustomAuthenticationStateProvider>());
builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddAuthorization();
builder.Services.AddAuthorizationCore();

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

// HTTP Client for API (with automatic token refresh)
builder.Services.AddScoped<ITokenStore, TokenStore>();
builder.Services.AddScoped<TokenRefreshHandler>();
builder.Services.AddHttpClient<IApiClient, ApiClient>();

var apiBaseUrl = builder.Configuration["ApiClient:BaseUrl"]
    ?? throw new InvalidOperationException("Missing configuration: ApiClient:BaseUrl");

builder.Services.AddHttpClient<IAdminDashboardClient, AdminDashboardClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IInquiryBrowserClient, InquiryBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IClientBrowserClient, ClientBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<ITaxFilingBrowserClient, TaxFilingBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IFaqBrowserClient, FaqBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IAnnouncementBrowserClient, AnnouncementBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();

// Phase 5: Tax Accounting & CRM Browser Clients
builder.Services.AddHttpClient<ITaxProfileBrowserClient, TaxProfileBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();

builder.Services.AddHttpClient<ITaxFilingScheduleBrowserClient, TaxFilingScheduleBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();

builder.Services.AddHttpClient<IConsultingActivityBrowserClient, ConsultingActivityBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();

builder.Services.AddHttpClient<IContractBrowserClient, ContractBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();

builder.Services.AddHttpClient<IRevenueTrackingBrowserClient, RevenueTrackingBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();

builder.Services.AddHttpClient<ICommonCodeBrowserClient, CommonCodeBrowserClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
}).AddHttpMessageHandler<TokenRefreshHandler>();

// 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.Configure<PortalAuthOptions>(builder.Configuration.GetSection("Authentication"));

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

builder.Services.AddInfrastructure();
builder.Services.AddApplication();
builder.Services.AddScoped<IInquiryNotificationService, TelegramInquiryNotificationService>();

// Register version info
var versionInfo = new VersionInfo();
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("/taxbaik/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}");
}

app.UsePathBase("/taxbaik");
app.UseResponseCompression();
app.UseStaticFiles();
app.UseSession();  // TempData 쿠키 저장소
app.UseRouting();
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();

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

// API + Razor Pages + Blazor 매핑
app.MapControllers();  // 기존 Controllers
app.MapFastEndpoints(); // 신규 FastEndpoints
app.MapHealthChecks("/healthz");
app.MapRazorPages();
app.MapStaticAssets();

// AllowAnonymous: JWT 미들웨어가 Blazor 셸 요청을 401로 차단하지 않도록 한다.
// 인증은 Blazor AuthorizeRouteView → RedirectToLogin 에서 처리한다.
// Phase 8: WebAssembly 렌더 모드 완전 마이그레이션
// - App.razor: TaxBaik.Web (메인 웹 서버)
// - Routes + Pages + Shared + Layout + Forms: TaxBaik.Web (메인 웹 서버)
// 모든 Blazor 컴포넌트가 웹 서버에서 통합 서비스됨
// API는 웹 서버에서만 제공 (클라이언트 프로젝트 분리 불필요)
app.MapRazorComponents<TaxBaik.Web.Components.Admin.App>()
   .AddInteractiveWebAssemblyRenderMode()
   .AllowAnonymous();

// 애플리케이션 시작/종료 로깅
try
{
    Log.Information("애플리케이션 시작: {Environment}", app.Environment.EnvironmentName);
    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "애플리케이션 강종");
    if (!app.Environment.IsDevelopment())
    {
        try
        {
            var fatalMessage = $"환경: {app.Environment.EnvironmentName}\n오류: {ex.Message}";
            if (TaxBaik.Web.Services.TelegramAlertGate.ShouldSend("telegram:fatal", fatalMessage, TimeSpan.FromMinutes(30)))
            {
                using var scope = app.Services.CreateScope();
                var telegramService = scope.ServiceProvider.GetRequiredService<ITelegramNotificationService>();
                await telegramService.SendErrorAsync(
                    "❌ 서버 오류",
                    fatalMessage);
            }
        }
        catch (Exception telegramEx)
        {
            Log.Error(telegramEx, "오류 알림 전송 실패");
        }
    }
    throw;
}
finally
{
    Log.Information("애플리케이션 종료");
    Log.CloseAndFlush();
}
