using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using UglyToad.PdfPig;

namespace AxCopilot.Services.Agent;

public partial class DocumentReaderTool
{
    // ─── BibTeX / RIS / DOCX / XLSX / Text / Helpers ─────────────────────

    // ─── BibTeX ─────────────────────────────────────────────────────────────

    private static string ReadBibTeX(string path, int maxChars)
    {
        var content = File.ReadAllText(path, Encoding.UTF8);
        var sb = new StringBuilder();

        var entryPattern = new Regex(
            @"@(\w+)\s*\{\s*([^,\s]+)\s*,\s*(.*?)\n\s*\}",
            RegexOptions.Singleline);

        var fieldPattern = new Regex(
            @"(\w+)\s*=\s*[\{""](.*?)[\}""]",
            RegexOptions.Singleline);

        var matches = entryPattern.Matches(content);
        sb.AppendLine($"BibTeX: {matches.Count}개 항목");
        sb.AppendLine();

        int idx = 0;
        foreach (Match m in matches)
        {
            if (sb.Length >= maxChars) break;
            idx++;

            var entryType = m.Groups[1].Value;
            var citeKey = m.Groups[2].Value;
            var body = m.Groups[3].Value;

            sb.AppendLine($"[{idx}] @{entryType}{{{citeKey}}}");

            var fields = fieldPattern.Matches(body);
            foreach (Match f in fields)
            {
                var fieldName = f.Groups[1].Value.ToLower();
                var fieldValue = f.Groups[2].Value.Trim();

                // 핵심 필드만 표시
                if (fieldName is "author" or "title" or "journal" or "booktitle"
                    or "year" or "volume" or "number" or "pages" or "doi"
                    or "publisher" or "url")
                {
                    sb.AppendLine($"  {fieldName}: {fieldValue}");
                }
            }
            sb.AppendLine();
        }

        if (matches.Count == 0)
        {
            sb.AppendLine("(BibTeX 항목을 파싱하지 못했습니다. 원문을 반환합니다.)");
            sb.AppendLine(Truncate(content, maxChars - sb.Length));
        }

        return Truncate(sb.ToString(), maxChars);
    }

    // ─── RIS ────────────────────────────────────────────────────────────────

    private static string ReadRis(string path, int maxChars)
    {
        var lines = File.ReadAllLines(path, Encoding.UTF8);
        var sb = new StringBuilder();

        var entries = new List<Dictionary<string, List<string>>>();
        Dictionary<string, List<string>>? current = null;

        foreach (var line in lines)
        {
            if (line.StartsWith("TY  -"))
            {
                current = new Dictionary<string, List<string>>();
                entries.Add(current);
            }
            else if (line.StartsWith("ER  -"))
            {
                current = null;
                continue;
            }

            if (current != null && line.Length >= 6 && line[2] == ' ' && line[3] == ' ' && line[4] == '-' && line[5] == ' ')
            {
                var tag = line[..2].Trim();
                var value = line[6..].Trim();
                if (!current.ContainsKey(tag))
                    current[tag] = new List<string>();
                current[tag].Add(value);
            }
        }

        sb.AppendLine($"RIS: {entries.Count}개 항목");
        sb.AppendLine();

        // RIS 태그 → 사람이 읽을 수 있는 이름
        var tagNames = new Dictionary<string, string>
        {
            ["TY"] = "Type", ["AU"] = "Author", ["TI"] = "Title", ["T1"] = "Title",
            ["JO"] = "Journal", ["JF"] = "Journal", ["PY"] = "Year", ["Y1"] = "Year",
            ["VL"] = "Volume", ["IS"] = "Issue", ["SP"] = "Start Page", ["EP"] = "End Page",
            ["DO"] = "DOI", ["UR"] = "URL", ["PB"] = "Publisher", ["AB"] = "Abstract",
            ["KW"] = "Keyword", ["SN"] = "ISSN/ISBN",
        };

        for (int i = 0; i < entries.Count && sb.Length < maxChars; i++)
        {
            sb.AppendLine($"[{i + 1}]");
            var entry = entries[i];
            foreach (var (tag, values) in entry)
            {
                var label = tagNames.GetValueOrDefault(tag, tag);
                if (tag is "AU" or "KW")
                    sb.AppendLine($"  {label}: {string.Join("; ", values)}");
                else
                    sb.AppendLine($"  {label}: {string.Join(" ", values)}");
            }
            sb.AppendLine();
        }

        return Truncate(sb.ToString(), maxChars);
    }

    // ─── DOCX ───────────────────────────────────────────────────────────────

    private static string ReadDocx(string path, int maxChars)
    {
        var sb = new StringBuilder();
        using var doc = WordprocessingDocument.Open(path, false);
        var body = doc.MainDocumentPart?.Document.Body;
        if (body == null) return "(빈 문서)";

        foreach (var para in body.Elements<DocumentFormat.OpenXml.Wordprocessing.Paragraph>())
        {
            var text = para.InnerText;
            if (!string.IsNullOrWhiteSpace(text))
            {
                sb.AppendLine(text);
                if (sb.Length >= maxChars) break;
            }
        }

        return Truncate(sb.ToString(), maxChars);
    }

    // ─── XLSX ───────────────────────────────────────────────────────────────

    private static string ReadXlsx(string path, string sheetParam, int maxChars)
    {
        var sb = new StringBuilder();
        using var doc = SpreadsheetDocument.Open(path, false);
        var workbook = doc.WorkbookPart;
        if (workbook == null) return "(빈 스프레드시트)";

        var sheets = workbook.Workbook.Sheets?.Elements<Sheet>().ToList() ?? [];
        if (sheets.Count == 0) return "(시트 없음)";

        sb.AppendLine($"Excel: {sheets.Count}개 시트 ({string.Join(", ", sheets.Select(s => s.Name?.Value))})");
        sb.AppendLine();

        Sheet? targetSheet = null;
        if (!string.IsNullOrEmpty(sheetParam))
        {
            if (int.TryParse(sheetParam, out var idx) && idx >= 1 && idx <= sheets.Count)
                targetSheet = sheets[idx - 1];
            else
                targetSheet = sheets.FirstOrDefault(s =>
                    string.Equals(s.Name?.Value, sheetParam, StringComparison.OrdinalIgnoreCase));
        }
        targetSheet ??= sheets[0];

        var sheetId = targetSheet.Id?.Value;
        if (sheetId == null) return "(시트 ID 없음)";

        var wsPart = (WorksheetPart)workbook.GetPartById(sheetId);
        var sharedStrings = workbook.SharedStringTablePart?.SharedStringTable
            .Elements<SharedStringItem>().ToList() ?? [];

        var rows = wsPart.Worksheet.Descendants<Row>().ToList();
        sb.AppendLine($"[{targetSheet.Name?.Value}] ({rows.Count} rows)");

        foreach (var row in rows)
        {
            var cells = row.Elements<Cell>().ToList();
            var values = new List<string>();
            foreach (var cell in cells)
                values.Add(GetCellValue(cell, sharedStrings));
            sb.AppendLine(string.Join("\t", values));
            if (sb.Length >= maxChars) break;
        }

        return Truncate(sb.ToString(), maxChars);
    }

    private static string GetCellValue(Cell cell, List<SharedStringItem> sharedStrings)
    {
        var value = cell.CellValue?.Text ?? "";
        if (cell.DataType?.Value == CellValues.SharedString)
        {
            if (int.TryParse(value, out var idx) && idx >= 0 && idx < sharedStrings.Count)
                return sharedStrings[idx].InnerText;
        }
        return value;
    }

    // ─── Text ───────────────────────────────────────────────────────────────

    private static async Task<string> ReadTextFile(string path, int maxChars, CancellationToken ct)
    {
        var text = await File.ReadAllTextAsync(path, Encoding.UTF8, ct);
        return Truncate(text, maxChars);
    }

    // ─── Helpers ────────────────────────────────────────────────────────────

    private static string Truncate(string text, int maxChars)
    {
        if (text.Length <= maxChars) return text;
        return text[..maxChars] + "\n\n... (내용 잘림 — pages 또는 section 파라미터로 특정 부분을 읽을 수 있습니다)";
    }

    private static string FormatSize(long bytes) => bytes switch
    {
        < 1024 => $"{bytes} B",
        < 1024 * 1024 => $"{bytes / 1024.0:F1} KB",
        _ => $"{bytes / (1024.0 * 1024.0):F1} MB",
    };

    /// <summary>JsonElement에서 int를 안전하게 추출합니다. string/integer 양쪽 호환.</summary>
    private static int GetIntValue(JsonElement el, int defaultValue)
    {
        if (el.ValueKind == JsonValueKind.Number) return el.GetInt32();
        if (el.ValueKind == JsonValueKind.String && int.TryParse(el.GetString(), out var v)) return v;
        return defaultValue;
    }
}
