using Opc.Ua;
using Opc.Ua.Client;
using OpcUaManager.Models;

namespace OpcUaManager.Services;

/// <summary>
/// 원본 HoneywellCrawler 를 Service 로 분리.
/// v1.5.374.70: BrowseNextAsync 가 튜플 반환이 아닌 BrowseNextResponse 객체 반환으로 변경됨.
/// </summary>
public class OpcCrawlerService
{
    private readonly ILogger<OpcCrawlerService> _logger;
    private readonly OpcSessionService          _sessionSvc;

    public OpcCrawlerService(ILogger<OpcCrawlerService> logger, OpcSessionService sessionSvc)
    {
        _logger     = logger;
        _sessionSvc = sessionSvc;
    }

    public async Task<CrawlResult> CrawlAsync(CrawlRequest req)
    {
        if (!_sessionSvc.IsConnected)
            return new CrawlResult { Success = false, Message = "세션이 연결되어 있지 않습니다." };

        var session = _sessionSvc.GetRawSession();
        if (session == null)
            return new CrawlResult { Success = false, Message = "Raw 세션을 가져올 수 없습니다." };

        var tags = new List<TagMaster>();

        try
        {
            _logger.LogInformation("저인망 탐사 시작: {NodeId}", req.StartNodeId);
            NodeId rootNode = NodeId.Parse(req.StartNodeId);
            await BrowseRecursiveAsync(session, rootNode, 0, req.MaxDepth, tags);

            string csvPath = await SaveToCsvAsync(tags);

            _logger.LogInformation("탐사 완료: {Count}개 노드", tags.Count);
            return new CrawlResult
            {
                Success    = true,
                Message    = $"탐사 완료: {tags.Count}개 노드 발견",
                TotalNodes = tags.Count,
                Tags       = tags,
                CsvPath    = csvPath
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Crawler 실행 오류");
            return new CrawlResult { Success = false, Message = ex.Message, Tags = tags };
        }
    }

    private async Task BrowseRecursiveAsync(
        Session session, NodeId nodeId, int level, int maxDepth, List<TagMaster> tags)
    {
        try
        {
            // 원본 동일: BrowseAsync 객체 방식
            BrowseDescription description = new()
            {
                NodeId          = nodeId,
                BrowseDirection = BrowseDirection.Forward,
                ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
                IncludeSubtypes = true,
                NodeClassMask   = (uint)(NodeClass.Variable | NodeClass.Object),
                ResultMask      = (uint)BrowseResultMask.All
            };

            BrowseResponse response = await session.BrowseAsync(
                null, null, 0, [description], default);

            if (response?.Results == null || response.Results.Count == 0) return;

            foreach (var result in response.Results)
            {
                await ProcessReferencesAsync(session, result.References, level, maxDepth, tags);

                byte[] cp = result.ContinuationPoint;
                while (cp != null && cp.Length > 0)
                {
                    // FIX CS8130/CS1503: BrowseNextAsync 튜플 방식 → BrowseNextResponse 객체 방식
                    // v1.5.374.70 에서 반환 타입이 Task<BrowseNextResponse> 로 변경됨
                    BrowseNextResponse nextResponse = await session.BrowseNextAsync(
                        null,
                        false,
                        new ByteStringCollection { cp },
                        default);

                    if (nextResponse?.Results != null && nextResponse.Results.Count > 0)
                    {
                        var nextResult = nextResponse.Results[0];
                        await ProcessReferencesAsync(session, nextResult.References, level, maxDepth, tags);
                        cp = nextResult.ContinuationPoint;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            // 원본과 동일: 특정 노드 권한 에러 무시
            _logger.LogDebug("노드 탐색 건너뜀 [{Level}] {NodeId}: {Msg}", level, nodeId, ex.Message);
        }
    }

    private async Task ProcessReferencesAsync(
        Session session,
        ReferenceDescriptionCollection? references,
        int level,
        int maxDepth,
        List<TagMaster> tags)
    {
        if (references == null || references.Count == 0) return;

        foreach (var rd in references)
        {
            NodeId childId = ExpandedNodeId.ToNodeId(rd.NodeId, session.NamespaceUris);

            tags.Add(new TagMaster
            {
                TagName    = rd.BrowseName.Name ?? "Unknown",
                FullNodeId = childId.ToString(),
                NodeClass  = rd.NodeClass.ToString(),
                Level      = level
            });

            _logger.LogDebug("{Indent}[{Class}] {Name} ({Id})",
                new string(' ', level * 2), rd.NodeClass, rd.BrowseName.Name, childId);

            if (rd.NodeClass == NodeClass.Object && level < maxDepth)
                await BrowseRecursiveAsync(session, childId, level + 1, maxDepth, tags);
        }
    }

    private async Task<string> SaveToCsvAsync(List<TagMaster> tags)
    {
        string path = Path.GetFullPath("Honeywell_FullMap.csv");
        try
        {
            await using var sw = new StreamWriter(path);
            await sw.WriteLineAsync("Level,Class,Name,NodeId");
            foreach (var tag in tags)
                await sw.WriteLineAsync($"{tag.Level},{tag.NodeClass},{tag.TagName},{tag.FullNodeId}");

            _logger.LogInformation("CSV 저장 완료: {Path}", path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "CSV 저장 실패");
        }
        return path;
    }
}
