using Journal.Core.Dtos;
using Journal.Core.Repositories;

namespace Journal.Core.Services.Entries;

public sealed class EntryFileService(IEntryFileRepository repo) : IEntryFileService
{
    private readonly IEntryFileRepository _repo = repo ?? throw new ArgumentNullException(nameof(repo));

    public IReadOnlyList<EntryListItem> ListEntries()
    {
        return [.. _repo.ListMarkdownFiles()
            .Where(path => !EntryFileNaming.IsTemplateFileName(_repo.GetFileName(path)))
            .Select(path => new EntryListItem(
                FileName: _repo.GetFileName(path),
                FilePath: _repo.GetFullPath(path)))];
    }

    public IReadOnlyList<EntryListItem> ListTemplates()
    {
        return [.. _repo.ListMarkdownFiles()
            .Where(path => EntryFileNaming.IsTemplateFileName(_repo.GetFileName(path)))
            .Select(path => new EntryListItem(
                FileName: _repo.GetFileName(path),
                FilePath: _repo.GetFullPath(path)))];
    }

    public EntryLoadResult LoadEntry(string filePath)
    {
        var normalizedPath = _repo.GetFullPath(filePath);
        if (!_repo.FileExists(normalizedPath))
            throw new FileNotFoundException($"Entry file not found: {normalizedPath}");

        var rawContent = HtmlSanitizer.StripRichHtml(_repo.ReadFile(normalizedPath));
        var fileStem = _repo.GetFileNameWithoutExtension(normalizedPath);
        var entry = JournalParser.ParseJournalContent(rawContent, fileStem);

        return new EntryLoadResult(
            FileName: _repo.GetFileName(normalizedPath),
            FilePath: normalizedPath,
            Entry: entry.ToDto());
    }

    public EntryTemplateLoadResult LoadTemplate(string filePath)
    {
        var normalizedPath = _repo.GetFullPath(filePath);
        if (!_repo.FileExists(normalizedPath))
            throw new FileNotFoundException($"Template file not found: {normalizedPath}");

        var fileName = _repo.GetFileName(normalizedPath);
        if (!EntryFileNaming.IsTemplateFileName(fileName))
            throw new ArgumentException("Template file name must end with .template.md.");

        var rawContent = HtmlSanitizer.StripRichHtml(_repo.ReadFile(normalizedPath));
        return new EntryTemplateLoadResult(fileName, normalizedPath, rawContent);
    }

    public EntrySaveResult SaveEntry(EntrySavePayload payload)
    {
        var targetPath = ResolveTargetPath(payload.FilePath, payload.FileName);
        var mode = string.IsNullOrWhiteSpace(payload.Mode) ? "Daily" : payload.Mode.Trim();
        var sanitizedContent = HtmlSanitizer.StripRichHtml(payload.Content ?? "");
        _repo.EnsureDirectory(targetPath);

        if (string.Equals(mode, "Overwrite", StringComparison.OrdinalIgnoreCase))
        {
            _repo.WriteFile(targetPath, sanitizedContent);
            return new EntrySaveResult(targetPath);
        }

        if (string.Equals(mode, "Fragment", StringComparison.OrdinalIgnoreCase))
        {
            _repo.AppendFile(targetPath, "\n\n" + sanitizedContent.Trim());
            return new EntrySaveResult(targetPath);
        }

        string finalContent;
        if (_repo.FileExists(targetPath))
        {
            var existingContent = _repo.ReadFile(targetPath);
            var fileStem = _repo.GetFileNameWithoutExtension(targetPath);
            var existingEntry = JournalParser.ParseJournalContent(existingContent, fileStem);
            var newEntryData = JournalParser.ParseJournalContent(sanitizedContent, fileStem);
            existingEntry.MergeWith(newEntryData);
            finalContent = existingEntry.ToMarkdown();
        }
        else
        {
            finalContent = sanitizedContent;
        }

        _repo.WriteFile(targetPath, finalContent);
        return new EntrySaveResult(targetPath);
    }

    public EntrySaveResult SaveTemplate(EntryTemplateSavePayload payload)
    {
        ArgumentNullException.ThrowIfNull(payload);
        if (string.IsNullOrWhiteSpace(payload.Name))
            throw new ArgumentException("Template name is required.");

        var targetPath = ResolveTemplatePath(payload.FilePath, payload.Name);
        var fileName = _repo.GetFileName(targetPath);
        if (!EntryFileNaming.IsTemplateFileName(fileName))
            throw new ArgumentException("Template file name must end with .template.md.");

        var sanitizedContent = HtmlSanitizer.StripRichHtml(payload.Content ?? "");
        _repo.EnsureDirectory(targetPath);
        _repo.WriteFile(targetPath, sanitizedContent);
        return new EntrySaveResult(targetPath);
    }

    public bool DeleteEntry(string filePath)
    {
        var normalizedPath = _repo.GetFullPath(filePath);
        if (!_repo.FileExists(normalizedPath))
            return false;
        _repo.DeleteFile(normalizedPath);
        return true;
    }

    public bool DeleteTemplate(string filePath)
    {
        var normalizedPath = _repo.GetFullPath(filePath);
        if (!_repo.FileExists(normalizedPath))
            return false;

        var fileName = _repo.GetFileName(normalizedPath);
        if (!EntryFileNaming.IsTemplateFileName(fileName))
            return false;

        _repo.DeleteFile(normalizedPath);
        return true;
    }

    private string ResolveTargetPath(string? filePath, string? fileName)
    {
        if (!string.IsNullOrWhiteSpace(filePath))
            return _repo.GetFullPath(filePath);

        var name = !string.IsNullOrWhiteSpace(fileName)
            ? SanitizeFileName(fileName)
            : $"{DateTime.Now:yyyy-MM-dd}";

        return _repo.GetFullPath($"{name}.md");
    }

    private string ResolveTemplatePath(string? filePath, string templateName)
    {
        if (!string.IsNullOrWhiteSpace(filePath))
            return _repo.GetFullPath(filePath);

        var name = SanitizeFileName(templateName);
        return _repo.GetFullPath($"{name}{EntryFileNaming.TemplateSuffix}");
    }

    private static string SanitizeFileName(string name)
    {
        var trimmed = name.Trim();
        if (trimmed.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
            trimmed = trimmed[..^3];

        var invalid = Path.GetInvalidFileNameChars();
        var sanitized = new string(trimmed.Select(c => Array.IndexOf(invalid, c) >= 0 ? '_' : c).ToArray());
        return string.IsNullOrWhiteSpace(sanitized) ? "untitled" : sanitized;
    }
}
