using ExperionCrawler.Core.Application.DTOs;
using ExperionCrawler.Core.Application.Interfaces;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Npgsql;

namespace ExperionCrawler.Core.Application.Services;

/// <summary>
/// Text-to-SQL 시계열 쿼리 서비스
/// 자연어 질의를 파싱하여 iiot-timescaledb(IIoT 플랫폼) SQL 쿼리를 생성하고 실행합니다.
/// measurements (TimeScaleDB 하이퍼테이블) + opc_nodes 테이블을 참조합니다.
/// </summary>
public class TextToSqlService : ITextToSqlService
{
    private readonly ILogger<TextToSqlService> _logger;
    private readonly IConfiguration _configuration;
    private readonly string _connectionString;

    public TextToSqlService(ILogger<TextToSqlService> logger, IConfiguration configuration)
    {
        _logger = logger;
        _configuration = configuration;
        _connectionString = configuration.GetConnectionString("DefaultConnection")
            ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
    }

    // ── 테이블명 설정 (iiot-timescaledb 매핑) ─────────────────────────────────
    // iiot-timescaledb: measurements (node_id, time, value, quality)
    // ExperionCrawler:  history_hypertable (tagname, recorded_at, value)
    private string HistoryTable => "measurements";
    private string NodesTable   => "opc_nodes";

    // ── 자연어 파싱 ─────────────────────────────────────────────────────────────

    public Task<string> ParseNaturalLanguageAsync(string input)
    {
        if (string.IsNullOrWhiteSpace(input))
            throw new ArgumentException("쿼리 입력이 필요합니다.", nameof(input));

        var sql = BuildSqlFromNaturalLanguage(input);
        _logger.LogInformation("[TextToSql] 자연어 파싱: \"{Input}\" → {Sql}", input, sql);
        return Task.FromResult(sql);
    }

    /// <summary>
    /// 자연어 입력에서 태그명, 시간 범위, 집계 함수를 추출하여 SQL 생성
    /// </summary>
    private string BuildSqlFromNaturalLanguage(string input)
    {
        var lower = input.ToLower();

        // 태그명 추출 (한글/영문 태그명 지원)
        var tagName = ExtractTagName(input);

        // 시간 범위 추출
        var (fromClause, timeBucket) = ExtractTimeRange(lower);

        // 집계 함수 추출
        var aggregate = ExtractAggregate(lower);

        // measurements (iiot-timescaledb 하이퍼테이블) 조회 SQL 생성
        // 컬럼: node_id (TEXT), time (TIMESTAMPTZ), value (DOUBLE PRECISION)
        if (!string.IsNullOrEmpty(tagName))
        {
            // SQL 인젝션 방지를 위해 태그명 이스케이프
            var escapedTagName = tagName.Replace("'", "''");
            var whereTag = $"WHERE node_id = '{escapedTagName}'";
            if (!string.IsNullOrEmpty(fromClause))
                whereTag += $" AND {fromClause}";

            if (aggregate != "last" && aggregate != "first")
            {
                // 집계 쿼리 (value는 이미 double precision)
                return $"SELECT time_bucket('{timeBucket}', time) AS bucket, " +
                       $"{aggregate}(value) AS result " +
                       $"FROM {HistoryTable} " +
                       $"{whereTag} " +
                       $"GROUP BY bucket ORDER BY bucket";
            }
            else
            {
                // first/last 쿼리 (TimeScaleDB 함수)
                var func = aggregate == "first" ? "first" : "last";
                return $"SELECT time_bucket('{timeBucket}', time) AS bucket, " +
                       $"{func}(value, time) AS result " +
                       $"FROM {HistoryTable} " +
                       $"{whereTag} " +
                       $"GROUP BY bucket ORDER BY bucket";
            }
        }

        // 태그명 없이 전체 조회
        var whereAll = fromClause ?? "1=1";
        return $"SELECT time_bucket('{timeBucket}', time) AS bucket, " +
               $"node_id, {aggregate}(value) AS result " +
               $"FROM {HistoryTable} " +
               $"WHERE {whereAll} " +
               $"GROUP BY bucket, node_id ORDER BY bucket, node_id";
    }

    /// <summary>
    /// 자연어에서 태그명/node_id 추출
    /// 예: "Reactor.Temperature 온도 최근 1시간 평균" → "Reactor.Temperature"
    ///      "ns=2;s=Reactor.Temperature 온도 최근 1시간 평균" → "ns=2;s=Reactor.Temperature"
    /// </summary>
    private string ExtractTagName(string input)
    {
        var lower = input.ToLower();

        // 키워드 목록 (시간/집계 관련 단어)
        var keywords = new[]
        {
            "최근", "last", "latest",
            "평균", "average", "avg", "평균값",
            "최대", "maximum", "max", "최댓값",
            "최소", "minimum", "min", "최솟값",
            "첫번째", "first", "초기값",
            "마지막", "last", "종료값",
            "추세", "trend",
            "데이터", "조회", "quer", "query",
            "시간", "hour", "hr",
            "분", "minute", "min", "m",
            "초", "second", "sec", "s",
            "일", "day", "d",
            "주", "week", "w",
            "전체", "all",
            "온도", "temperature",
            "압력", "pressure",
            "유량", "flow", "유량", "flow rate",
            "수위", "level", "수위",
            "rpm", "전류", "current"
        };

        foreach (var kw in keywords)
        {
            var idx = lower.IndexOf(kw);
            if (idx > 0)
            {
                var candidate = input.Substring(0, idx).Trim();
                // OPC UA node_id 형식(ns=...;s=...) 또는 일반 태그명 반환
                return candidate;
            }
        }

        // OPC UA node_id 형식 확인 (ns=X;s=...)
        var nsMatch = System.Text.RegularExpressions.Regex.Match(input, @"ns=\d+;s=[^ ]+");
        if (nsMatch.Success)
            return nsMatch.Value;

        // 키워드가 없으면 첫 단어(공백 이전)를 태그명으로 간주
        var firstSpace = input.IndexOf(' ');
        if (firstSpace > 0)
            return input.Substring(0, firstSpace).Trim();

        return input.Trim();
    }

    /// <summary>
    /// 자연어에서 시간 범위 추출
    /// </summary>
    private (string? whereClause, string timeBucket) ExtractTimeRange(string lower)
    {
        // 시간 단위 매핑 (measurements 테이블의 time 컬럼 사용)
        var timePatterns = new (string pattern, string interval, string bucket)[]
        {
            ("최근 1시간", "1 hour", "5 min"),
            ("최근 2시간", "2 hours", "5 min"),
            ("최근 3시간", "3 hours", "10 min"),
            ("최근 6시간", "6 hours", "10 min"),
            ("최근 12시간", "12 hours", "30 min"),
            ("최근 24시간", "24 hours", "1 hour"),
            ("최근 하루", "24 hours", "1 hour"),
            ("최근 1일", "1 day", "1 hour"),
            ("최근 3일", "3 days", "6 hours"),
            ("최근 7일", "7 days", "12 hours"),
            ("최근 1주", "7 days", "12 hours"),
            ("최근 1개월", "30 days", "1 day"),
            ("최근 한달", "30 days", "1 day"),
            ("오늘", "1 day", "1 hour"),
            ("어제", "1 day", "1 hour"),
            ("최근 5분", "5 minutes", "1 min"),
            ("최근 10분", "10 minutes", "1 min"),
            ("최근 30분", "30 minutes", "1 min"),
        };

        foreach (var (pattern, interval, bucket) in timePatterns)
        {
            if (lower.Contains(pattern))
            {
                return ($"time > NOW() - INTERVAL '{interval}'", bucket);
            }
        }

        // "from ~ to" 패턴 (ISO 8601 형식 또는 한국어)
        var fromMatch = System.Text.RegularExpressions.Regex.Match(lower, @"from\s+(\d{4}-\d{2}-\d{2})");
        var toMatch = System.Text.RegularExpressions.Regex.Match(lower, @"to\s+(\d{4}-\d{2}-\d{2})");
        if (fromMatch.Success)
        {
            var from = fromMatch.Groups[1].Value;
            var to = toMatch.Success ? toMatch.Groups[1].Value : DateTime.Now.ToString("yyyy-MM-dd");
            return ($"time >= '{from} 00:00:00' AND time <= '{to} 23:59:59'", "1 hour");
        }

        return (null, "5 min");
    }

    /// <summary>
    /// 자연어에서 집계 함수 추출
    /// </summary>
    private string ExtractAggregate(string lower)
    {
        if (lower.Contains("최대") || lower.Contains("max") || lower.Contains("최댓값"))
            return "max";
        if (lower.Contains("최소") || lower.Contains("min") || lower.Contains("최솟값"))
            return "min";
        if (lower.Contains("첫") || lower.Contains("first") || lower.Contains("초기"))
            return "first";
        if (lower.Contains("마지막") || lower.Contains("last") || lower.Contains("종료"))
            return "last";
        // 기본값: 평균
        return "avg";
    }

    // ── SQL 실행 ────────────────────────────────────────────────────────────────

    public async Task<SqlQueryResultDto> ExecuteQueryAsync(string sql, int? limit = 1000)
    {
        try
        {
            using var conn = new NpgsqlConnection(_connectionString);
            await conn.OpenAsync();

            var fullSql = sql.TrimEnd();
            if (limit.HasValue && !fullSql.EndsWith(";", StringComparison.OrdinalIgnoreCase) 
                                 && !fullSql.EndsWith(" limit ", StringComparison.OrdinalIgnoreCase))
            {
                fullSql += $" LIMIT {limit.Value}";
            }

            using var cmd = new NpgsqlCommand(fullSql, conn);
            using var reader = await cmd.ExecuteReaderAsync();

            var columns = new List<string>();
            for (int i = 0; i < reader.FieldCount; i++)
            {
                columns.Add(reader.GetName(i));
            }

            var rows = new List<Dictionary<string, object?>>();
            while (await reader.ReadAsync())
            {
                var row = new Dictionary<string, object?>();
                for (int i = 0; i < reader.FieldCount; i++)
                {
                    row[reader.GetName(i)] = reader.IsDBNull(i) ? null : reader.GetValue(i);
                }
                rows.Add(row);
            }

            _logger.LogInformation("[TextToSql] 쿼리 실행 성공: {RowCount}행", rows.Count);
            return new SqlQueryResultDto
            {
                Success = true,
                Columns = columns,
                Rows = rows,
                TotalCount = rows.Count
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[TextToSql] 쿼리 실행 실패: {Sql}", sql);
            return new SqlQueryResultDto
            {
                Success = false,
                Error = ex.Message
            };
        }
    }

    // ── 쿼리 제안 ───────────────────────────────────────────────────────────────

    public Task<IEnumerable<string>> SuggestQueriesAsync(string partialInput)
    {
        var suggestions = new List<string>
        {
            "최근 1시간 평균",
            "최근 24시간 최대값",
            "최근 7일 최소값",
            "전체 태그 현재 값",
            "오늘의 데이터 추세"
        };

        if (string.IsNullOrWhiteSpace(partialInput))
            return Task.FromResult<IEnumerable<string>>(suggestions);

        var lower = partialInput.ToLower();
        return Task.FromResult<IEnumerable<string>>(suggestions.Where(s => s.Contains(lower)));
    }

    // ── 시계열 분석 ─────────────────────────────────────────────────────────────

    public async Task<TimeSeriesAnalysisDto> AnalyzeAsync(AnalyzeRequestDto dto)
    {
        var result = new TimeSeriesAnalysisDto();

        try
        {
            using var conn = new NpgsqlConnection(_connectionString);
            await conn.OpenAsync();

            var tagNames = dto.TagNames.Count > 0
                ? dto.TagNames
                : await GetAllTagNamesAsync(conn);

            var from = dto.From?.ToString("yyyy-MM-dd HH:mm:ss") ?? DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd HH:mm:ss");
            var to = dto.To?.ToString("yyyy-MM-dd HH:mm:ss") ?? DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

            var tagResults = new List<AnalysisTagResult>();

            foreach (var tagName in tagNames)
            {
                // SQL 인젝션 방지를 위해 태그명 이스케이프
                var escapedTagName = tagName.Replace("'", "''");
                
                // measurements 테이블: node_id (TEXT), time (TIMESTAMPTZ), value (DOUBLE PRECISION)
                var sql = $@"
                    SELECT
                        AVG(value) AS avg_val,
                        MIN(value) AS min_val,
                        MAX(value) AS max_val,
                        first(value, time) AS first_val,
                        last(value, time) AS last_val,
                        COUNT(*) AS point_count
                    FROM measurements
                    WHERE node_id = '{escapedTagName}'
                      AND time BETWEEN '{from}' AND '{to}'";

                using var cmd = new NpgsqlCommand(sql, conn);
                using var reader = await cmd.ExecuteReaderAsync();

                if (await reader.ReadAsync())
                {
                    tagResults.Add(new AnalysisTagResult
                    {
                        TagName = tagName,
                        Avg = reader.IsDBNull(0) ? null : Convert.ToDouble(reader.GetValue(0)),
                        Min = reader.IsDBNull(1) ? null : Convert.ToDouble(reader.GetValue(1)),
                        Max = reader.IsDBNull(2) ? null : Convert.ToDouble(reader.GetValue(2)),
                        First = reader.IsDBNull(3) ? null : Convert.ToDouble(reader.GetValue(3)),
                        Last = reader.IsDBNull(4) ? null : Convert.ToDouble(reader.GetValue(4)),
                        PointCount = reader.IsDBNull(5) ? 0 : Convert.ToInt64(reader.GetValue(5)),
                        From = dto.From,
                        To = dto.To
                    });
                }
            }

            result.Tags = tagResults;
            result.Success = true;
            _logger.LogInformation("[TextToSql] 분석 완료: {TagCount}개 태그", tagResults.Count);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[TextToSql] 분석 실패");
            result.Success = false;
            result.Error = ex.Message;
        }

        return result;
    }

    private async Task<List<string>> GetAllTagNamesAsync(NpgsqlConnection conn)
    {
        var tags = new List<string>();
        try
        {
            // measurements 테이블에서 고유 node_id 목록 조회
            using var cmd = new NpgsqlCommand(
                "SELECT DISTINCT node_id FROM measurements ORDER BY node_id", conn);
            using var reader = await cmd.ExecuteReaderAsync();
            while (await reader.ReadAsync())
            {
                tags.Add(reader.GetString(0));
            }
        }
        catch
        {
            // fallback: empty list
        }
        return tags;
    }
}
