using ExperionCrawler.Core.Application.Interfaces;
using ExperionCrawler.Core.Application.Services;
using ExperionCrawler.Infrastructure.Certificates;
using ExperionCrawler.Infrastructure.Csv;
using ExperionCrawler.Infrastructure.Database;
using ExperionCrawler.Infrastructure.Kb;
using ExperionCrawler.Infrastructure.Mcp;
using ExperionCrawler.Infrastructure.OpcUa;
using ExperionCrawler.Infrastructure.Trend;
using ExperionCrawler.Web;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// ── MVC / Swagger ─────────────────────────────────────────────────────────────
var mvcBuilder = builder.Services.AddControllers()
    .AddJsonOptions(opt =>
    {
        // JSON 직렬화 시 대소문자 구분 없이 처리하도록 PascalCase 유지
        opt.JsonSerializerOptions.PropertyNamingPolicy = null;
        // Deserialize 시 camelCase 키를 C# PascalCase 속성에 매핑 (프론트엔드 호환)
        opt.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
    });
builder.Services.AddMemoryCache();

// ── P&ID 컨트롤러 조건부 활성화 (기본: 비활성화) ─────────────────────────────
// PidControllers:Enabled = true 로 설정 시 P&ID 관련 컨트롤러 활성화
bool pidEnabled = builder.Configuration.GetValue<bool>("PidControllers:Enabled");
if (!pidEnabled)
{
    var partManager = mvcBuilder.PartManager;
    var excludedNames = new[] { "PidController", "ExperionPidController", "PidGraphController" };
    var existingProvider = partManager.FeatureProviders
        .OfType<Microsoft.AspNetCore.Mvc.Controllers.ControllerFeatureProvider>()
        .FirstOrDefault();
    if (existingProvider != null)
    {
        partManager.FeatureProviders.Remove(existingProvider);
        partManager.FeatureProviders.Add(new ExcludedControllersFeatureProvider(existingProvider, excludedNames));
    }
}
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
    c.SwaggerDoc("v1", new() { Title = "ExperionCrawler API", Version = "v1" }));

// ── Infrastructure ────────────────────────────────────────────────────────────
builder.Services.AddSingleton<IExperionCertificateService, ExperionCertificateService>();
builder.Services.AddSingleton<IExperionStatusCodeService, ExperionStatusCodeService>();
builder.Services.AddSingleton<IOpcUaConfigProvider, OpcUaConfigProvider>();
builder.Services.AddScoped<IExperionOpcClient,  ExperionOpcClient>();
builder.Services.AddScoped<IExperionOpcWriteClient, ExperionOpcWriteClient>();
builder.Services.AddScoped<IExperionCsvService, ExperionCsvService>();
builder.Services.AddScoped<AssetLoader>();

// PostgreSQL – Ubuntu 서버에서 별도 설치 없이 동작
Directory.CreateDirectory("data");
builder.Services.AddDbContext<ExperionDbContext>(opt =>
    opt.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<IExperionDbService, ExperionDbService>();
builder.Services.AddScoped<ITrendService, TrendService>();

// ── Application Services ──────────────────────────────────────────────────────
builder.Services.AddScoped<ExperionCrawlService>();

// ── KST 시간대 관리 서비스 ──────────────────────────────────────────────────
builder.Services.AddSingleton<IClock, SystemClock>();
builder.Services.AddSingleton<KstClock>();

// ── 한글 시간 범위 추출기 ──────────────────────────────────────────────────
builder.Services.AddSingleton<KoreanTimeRangeExtractor>();

// ── Text-to-SQL Service ──────────────────────────────────────────────────────
builder.Services.AddSingleton<SqlValidatorOptions>(_ => new SqlValidatorOptions
{
    RequiredTables   = ["history_table"],
    AllowedTables    = ["history_table", "node_map_master", "realtime_table", "tag_metadata", "v_tag_summary"],
    MaxSubqueryDepth = 4
});
builder.Services.AddSingleton<SqlValidator>();
builder.Services.AddScoped<ITextToSqlService, TextToSqlService>();

// ── Realtime & History BackgroundServices ─────────────────────────────────────
builder.Services.AddSingleton<ExperionRealtimeService>();
builder.Services.AddSingleton<IExperionRealtimeService>(
    sp => sp.GetRequiredService<ExperionRealtimeService>());
builder.Services.AddHostedService(
    sp => sp.GetRequiredService<ExperionRealtimeService>());
builder.Services.AddHostedService<ExperionHistoryService>();
builder.Services.AddHostedService<DigitalEventDetectorService>();

// ── MCP Service ───────────────────────────────────────────────────────────────
// Python MCP 서버 (localhost:5001)와 통신
// McpClient: 저수준 HTTP 클라이언트 / McpService: IMcpService 구현 (McpClient 위임)
// IHttpClientFactory 패턴: 핸들러 재사용으로 소켓 고갈 방지, 기본 타임아웃 명시
builder.Services.AddHttpClient(McpClient.HttpClientName, c =>
{
    c.BaseAddress = new Uri("http://localhost:5001");
    // HttpClient.Timeout = 하드 상한. McpClient 내부에서 소프트(120s)→연장(300s) 2단계 적용.
    // ExtendedTimeoutSeconds(300) + 네트워크/직렬화 여유 60s.
    c.Timeout = TimeSpan.FromSeconds(McpClient.ExtendedTimeoutSeconds + 60);
}).SetHandlerLifetime(TimeSpan.FromMinutes(5));
builder.Services.AddSingleton<McpClient>();
builder.Services.AddSingleton<IMcpService, McpService>();
builder.Services.AddHostedService<McpServerHostedService>();

// ── OPC UA Server BackgroundService ──────────────────────────────────────────
builder.Services.AddSingleton<ExperionOpcServerService>();
builder.Services.AddSingleton<IExperionOpcServerService>(
    sp => sp.GetRequiredService<ExperionOpcServerService>());
builder.Services.AddHostedService(
    sp => sp.GetRequiredService<ExperionOpcServerService>());

// ── FastTable Service ─────────────────────────────────────────────────────────
// 중요: Singleton으로 하나만 생성 후 IExperionFastService와 IHostedService 양쪽에 같은 인스턴스 공유
builder.Services.AddSingleton<ExperionFastService>();
builder.Services.AddSingleton<IExperionFastService>(sp => sp.GetRequiredService<ExperionFastService>());
builder.Services.AddHostedService(sp => sp.GetRequiredService<ExperionFastService>());

// ── Metadata Loader Service ───────────────────────────────────────────────────
builder.Services.AddScoped<IMetadataLoaderService, MetadataLoaderService>();

// ── Feedforward Advisory Engine ───────────────────────────────────────────────
builder.Services.AddSingleton<ExperionCrawler.Infrastructure.Control.FeedforwardEngine>();
builder.Services.AddSingleton<ExperionCrawler.Core.Application.Feedforward.IFeedforwardAdvisoryStore, ExperionCrawler.Infrastructure.Control.FeedforwardAdvisoryStore>();
builder.Services.AddScoped<ExperionCrawler.Core.Application.Feedforward.IFeedforwardConfigStore, ExperionCrawler.Infrastructure.Control.FeedforwardConfigStore>();
builder.Services.AddScoped<ExperionCrawler.Core.Application.Feedforward.IFeedforwardAuditService, ExperionCrawler.Infrastructure.Control.FeedforwardAuditService>();
builder.Services.AddSingleton<ExperionCrawler.Core.Application.Feedforward.IFeedforwardWriteGuard, ExperionCrawler.Infrastructure.Control.FeedforwardWriteGuard>();
builder.Services.AddSingleton<ExperionCrawler.Infrastructure.Control.FeedforwardSupervisor>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<ExperionCrawler.Infrastructure.Control.FeedforwardSupervisor>());
// WP0/WP3: Sim Override(입력 치환) + Feed Ramp Advisor(read-only)
builder.Services.AddSingleton<ExperionCrawler.Core.Application.Feedforward.ISimOverrideStore, ExperionCrawler.Infrastructure.Control.SimOverrideStore>();
builder.Services.AddSingleton<ExperionCrawler.Core.Application.Feedforward.ICompositionStore, ExperionCrawler.Infrastructure.Control.CompositionStore>();
builder.Services.AddScoped<ExperionCrawler.Infrastructure.Control.FeedRampAdvisorService>();

// ── P&ID Services ───────────────────────────────────────────────────────────────
builder.Services.AddScoped<IPidExtractorService, PidExtractorService>();
builder.Services.AddScoped<ITagMappingService, TagMappingService>();
builder.Services.AddScoped<IPidGraphService, PidGraphService>();
builder.Services.AddDbContextFactory<ExperionDbContext>();
builder.Services.AddScoped<IStatusStore, DbStatusStore>();
builder.Services.AddSingleton<IPidGraphEventBroadcaster, PidGraphEventBroadcaster>();

// ── FastTable Cleanup Service ─────────────────────────────────────────────────
builder.Services.AddHostedService<ExperionFastCleanupService>();

// ── Knowledge Base (RAG) ──────────────────────────────────────────────────────
builder.Services.AddHttpClient("KbQdrant", (sp, c) =>
{
    var cfg = sp.GetRequiredService<IConfiguration>();
    var baseUrl = cfg["Kb:QdrantUrl"] ?? "http://localhost:6333";
    c.BaseAddress = new Uri(baseUrl);
    c.Timeout = TimeSpan.FromSeconds(30);
});
builder.Services.AddSingleton<KbQdrantClient>();
builder.Services.AddSingleton<KbStorageService>();
builder.Services.AddSingleton<KbEmbeddingClient>();
builder.Services.AddScoped<IKbAuthService, KbAuthService>();
builder.Services.AddSingleton<ExperionCrawler.Infrastructure.Docs.DocBrowserService>();
builder.Services.AddHostedService<KbStartupService>();
builder.Services.AddHostedService<KbIngestWorker>();

// ── Ollama HttpClient ─────────────────────────────────────────────────────────
builder.Services.AddHttpClient("Ollama", c =>
{
    c.BaseAddress = new Uri("http://localhost:11434");
    c.Timeout = TimeSpan.FromSeconds(1800);
}).SetHandlerLifetime(Timeout.InfiniteTimeSpan);

// ── vLLM HttpClient (OpenAI-compatible) ──────────────────────────────────────
builder.Services.AddHttpClient("Vllm", c =>
{
    c.BaseAddress = new Uri("http://localhost:8000");
    c.Timeout = TimeSpan.FromSeconds(1800);
}).SetHandlerLifetime(Timeout.InfiniteTimeSpan);

// ── CORS ──────────────────────────────────────────────────────────────────────
// appsettings.json 의 "Cors:AllowedOrigins" 배열을 화이트리스트로 사용.
// 미설정 또는 ["*"] 인 경우에만 AllowAnyOrigin (개발 편의). 운영에서는 명시적 origin 권장.
builder.Services.AddCors(opt =>
    opt.AddDefaultPolicy(p =>
    {
        var origins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>();
        if (origins == null || origins.Length == 0 || (origins.Length == 1 && origins[0] == "*"))
        {
            // 자격증명 미사용 + 내부망 가정: 와일드카드 허용 (역호환)
            p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
        }
        else
        {
            p.WithOrigins(origins).AllowAnyMethod().AllowAnyHeader();
        }
    }));

// ── 포트 설정 (Ubuntu 환경: 기본 5000) ───────────────────────────────────────
builder.WebHost.UseUrls("http://0.0.0.0:5000");

var app = builder.Build();

// ── DB 초기화 ─────────────────────────────────────────────────────────────────
try
{
    using (var scope = app.Services.CreateScope())
    {
        var db = scope.ServiceProvider.GetRequiredService<IExperionDbService>();
        await db.InitializeAsync();

        try
        {
            var kbAuth = scope.ServiceProvider.GetRequiredService<IKbAuthService>();
            await kbAuth.EnsureCredentialAsync();
        }
        catch (Exception kbEx)
        {
            var lg = app.Services.GetRequiredService<ILogger<Program>>();
            lg.LogWarning(kbEx, "[Kb] 관리자 비밀번호 초기화 실패");
        }
    }
}
catch (Exception ex)
{
    // DB 초기화 실패 시 앱 시작 계속 — 기능 사용 시 지연 초기화
    var logger = app.Services.GetRequiredService<ILogger<Program>>();
    logger.LogWarning(ex, "[DB] 초기화 실패 — DB 관련 기능 비활성화 (앱 시작 계속)");
}

// ── Middleware ────────────────────────────────────────────────────────────────
app.UseCors();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseDefaultFiles();   // index.html
app.UseStaticFiles();    // wwwroot/
app.MapControllers();
app.MapFallbackToFile("index.html");

app.Run();
