using ExperionCrawler.Core.Application.Interfaces;
using ExperionCrawler.Core.Domain.Entities;
using Microsoft.Extensions.Logging;

namespace ExperionCrawler.Core.Application.Services;

public class ExperionCrawlService
{
    private readonly IExperionOpcClient  _opcClient;
    private readonly IExperionCsvService _csvService;
    private readonly IExperionDbService  _dbService;
    private readonly ILogger<ExperionCrawlService> _logger;

    public ExperionCrawlService(
        IExperionOpcClient  opcClient,
        IExperionCsvService csvService,
        IExperionDbService  dbService,
        ILogger<ExperionCrawlService> logger)
    {
        _opcClient  = opcClient;
        _csvService = csvService;
        _dbService  = dbService;
        _logger     = logger;
    }

    /// <summary>
    /// 지정한 노드들을 intervalSeconds 간격으로 durationSeconds 동안 수집하고
    /// CSV 파일로 저장한다.
    /// </summary>
    public async Task<ExperionCrawlResult> RunCrawlAsync(
        ExperionServerConfig    config,
        IEnumerable<string>     nodeIds,
        int                     intervalSeconds,
        int                     durationSeconds,
        CancellationToken       ct = default)
    {
        var sessionId  = Guid.NewGuid().ToString("N")[..8];
        var allRecords = new List<ExperionRecord>();
        var nodeList   = nodeIds.ToList();
        var endAt      = DateTime.UtcNow.AddSeconds(durationSeconds);
        int iteration  = 0;

        _logger.LogInformation(
            "[ExperionCrawl] session={Session} nodes={Nodes} duration={Duration}s interval={Interval}s",
            sessionId, nodeList.Count, durationSeconds, intervalSeconds);

        while (DateTime.UtcNow < endAt && !ct.IsCancellationRequested)
        {
            iteration++;
            var results = await _opcClient.ReadTagsAsync(config, nodeList);

            allRecords.AddRange(results.Select(r => new ExperionRecord
            {
                NodeId      = r.NodeId,
                Value       = r.Value?.ToString(),
                StatusCode  = r.StatusCode,
                CollectedAt = r.Timestamp ?? DateTime.UtcNow,
                SessionId   = sessionId
            }));

            _logger.LogDebug("[ExperionCrawl] iter={Iter} collected={Count}", iteration, allRecords.Count);

            if (DateTime.UtcNow < endAt && !ct.IsCancellationRequested)
                await Task.Delay(TimeSpan.FromSeconds(intervalSeconds), ct);
        }

        // CSV 내보내기
        var csvName = $"experion_{sessionId}_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
        var csvPath = await _csvService.ExportAsync(allRecords, csvName);

        return new ExperionCrawlResult(sessionId, allRecords.Count, csvPath, allRecords);
    }

    /// <summary>
    /// OPC UA 서버의 전체 노드 트리를 재귀 탐색하여 노드맵 CSV로 저장.
    /// 생성된 CSV는 AssetLoader.ImportFullMapAsync로 DB에 적재 가능.
    /// </summary>
    public async Task<ExperionNodeMapCrawlResult> RunNodeMapCrawlAsync(
        ExperionServerConfig config,
        int                  maxDepth = 10,
        CancellationToken    ct       = default)
    {
        _logger.LogInformation("[ExperionCrawl] 노드맵 크롤 시작 (maxDepth={MaxDepth})", maxDepth);

        var mapResult = await _opcClient.BrowseAllNodesAsync(config, maxDepth, ct);

        if (!mapResult.Success)
            return new ExperionNodeMapCrawlResult(false, 0, string.Empty, mapResult.ErrorMessage ?? "탐색 실패");

        var csvName = $"{config.ServerHostName}_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
        var csvPath = await _csvService.ExportNodeMapAsync(mapResult.Nodes, csvName);

        _logger.LogInformation("[ExperionCrawl] 노드맵 크롤 완료: {Count}개 → {Path}", mapResult.TotalCount, csvPath);
        return new ExperionNodeMapCrawlResult(true, mapResult.TotalCount, csvPath, null);
    }

    /// <summary>CSV 파일 한 개를 읽어 DB에 삽입. truncate=true 이면 기존 레코드를 모두 삭제 후 삽입.</summary>
    public async Task<ExperionImportResult> ImportCsvToDbAsync(string csvFileName, bool truncate = false)
    {
        try
        {
            if (truncate)
                await _dbService.ClearRecordsAsync();

            var fullPath = Path.Combine("data/csv", csvFileName);
            var records  = (await _csvService.ImportAsync(fullPath)).ToList();
            var saved    = await _dbService.SaveRecordsAsync(records);
            return new ExperionImportResult(true, saved, $"{saved}개 레코드가 DB에 저장되었습니다.");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[ExperionCrawl] CSV import failed: {File}", csvFileName);
            return new ExperionImportResult(false, 0, $"오류: {ex.Message}");
        }
    }
}

public record ExperionCrawlResult(
    string SessionId,
    int    TotalRecords,
    string CsvPath,
    IEnumerable<ExperionRecord> Records);

public record ExperionImportResult(bool Success, int Count, string Message);
public record ExperionNodeMapCrawlResult(bool Success, int TotalCount, string CsvPath, string? ErrorMessage);
