using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using Tornado3_2026Election.Domain;

namespace Tornado3_2026Election.Services;

public sealed class CareerPromiseService
{
    private static readonly JsonSerializerOptions SerializerOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        AllowTrailingCommas = true,
        ReadCommentHandling = JsonCommentHandling.Skip,
        WriteIndented = true
    };

    private readonly LogService _logService;
    private CareerPromiseCatalog _catalog;

    public CareerPromiseService(LogService logService)
    {
        _logService = logService;
        FilePath = ResolveFilePath();
        TryMigrateLegacyCatalog();
        _catalog = LoadCatalog(FilePath);
    }

    public string FilePath { get; }

    public IReadOnlyList<CareerPromiseEntry> GetEntries(
        string stationId,
        string electionType,
        string districtCode,
        string districtName)
    {
        var normalizedElectionType = electionType?.Trim() ?? string.Empty;
        var normalizedDistrictCode = districtCode?.Trim() ?? string.Empty;
        var normalizedDistrictName = districtName?.Trim() ?? string.Empty;

        return SafeEntries(_catalog.Entries)
            .Where(entry =>
                string.Equals(entry.ElectionType, normalizedElectionType, StringComparison.Ordinal) &&
                MatchesDistrict(entry, normalizedDistrictCode, normalizedDistrictName))
            .ToArray();
    }

    public void SaveEntries(
        string stationId,
        string electionType,
        string districtCode,
        string districtName,
        IEnumerable<CareerPromiseEntry> entries)
    {
        ArgumentNullException.ThrowIfNull(entries);

        var normalizedStationId = stationId?.Trim() ?? string.Empty;
        var normalizedElectionType = electionType?.Trim() ?? string.Empty;
        var normalizedDistrictCode = districtCode?.Trim() ?? string.Empty;
        var normalizedDistrictName = districtName?.Trim() ?? string.Empty;

        var retainedEntries = SafeEntries(_catalog.Entries)
            .Where(entry =>
                !string.Equals(entry.ElectionType, normalizedElectionType, StringComparison.Ordinal) ||
                !MatchesDistrict(entry, normalizedDistrictCode, normalizedDistrictName))
            .ToList();

        retainedEntries.AddRange(
            entries
                .Select(entry => NormalizeEntry(
                    entry,
                    normalizedStationId,
                    normalizedElectionType,
                    normalizedDistrictCode,
                    normalizedDistrictName))
                .Where(entry => entry is not null)
                .Select(entry => entry!));

        _catalog = new CareerPromiseCatalog
        {
            Version = _catalog.Version,
            UpdatedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
            Entries = retainedEntries
                .OrderBy(entry => entry.StationId, StringComparer.OrdinalIgnoreCase)
                .ThenBy(entry => entry.ElectionType, StringComparer.Ordinal)
                .ThenBy(entry => entry.DistrictCode, StringComparer.OrdinalIgnoreCase)
                .ThenBy(entry => entry.CandidateName, StringComparer.Ordinal)
                .ThenBy(entry => entry.CandidateCode, StringComparer.OrdinalIgnoreCase)
                .ToArray()
        };

        SaveCatalog(FilePath, _catalog);
    }

    private static CareerPromiseEntry? NormalizeEntry(
        CareerPromiseEntry entry,
        string stationId,
        string electionType,
        string districtCode,
        string districtName)
    {
        var candidateName = entry.CandidateName?.Trim() ?? string.Empty;
        var party = entry.Party?.Trim() ?? string.Empty;
        var resolvedDistrictName = string.IsNullOrWhiteSpace(entry.DistrictName)
            ? districtName
            : entry.DistrictName.Trim();
        if (string.IsNullOrWhiteSpace(candidateName) ||
            string.IsNullOrWhiteSpace(party) ||
            string.IsNullOrWhiteSpace(resolvedDistrictName))
        {
            return null;
        }

        var promises = (entry.Promises ?? Array.Empty<string>())
            .Take(3)
            .Select(promise => promise?.Trim() ?? string.Empty)
            .ToArray();
        if (promises.All(string.IsNullOrWhiteSpace))
        {
            return null;
        }

        return new CareerPromiseEntry
        {
            StationId = stationId,
            ElectionType = electionType,
            DistrictCode = districtCode,
            DistrictName = resolvedDistrictName,
            CandidateCode = entry.CandidateCode?.Trim() ?? string.Empty,
            CandidateName = candidateName,
            Party = party,
            Promises = promises
        };
    }

    private static bool MatchesDistrict(
        CareerPromiseEntry entry,
        string districtCode,
        string districtName)
    {
        var normalizedEntryDistrictName = NormalizeLookupKey(entry.DistrictName);
        var normalizedDistrictName = NormalizeLookupKey(districtName);
        if (!string.IsNullOrWhiteSpace(normalizedDistrictName) &&
            !string.IsNullOrWhiteSpace(normalizedEntryDistrictName))
        {
            return string.Equals(normalizedEntryDistrictName, normalizedDistrictName, StringComparison.Ordinal);
        }

        var entryDistrictCode = entry.DistrictCode?.Trim() ?? string.Empty;
        return !string.IsNullOrWhiteSpace(districtCode) &&
            string.Equals(entryDistrictCode, districtCode, StringComparison.OrdinalIgnoreCase);
    }

    private static string NormalizeLookupKey(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        return string.Concat(value.Where(character => !char.IsWhiteSpace(character)))
            .ToUpperInvariant();
    }

    private CareerPromiseCatalog LoadCatalog(string filePath)
    {
        if (!File.Exists(filePath))
        {
            return new CareerPromiseCatalog();
        }

        try
        {
            var json = File.ReadAllText(filePath);
            return NormalizeCatalog(JsonSerializer.Deserialize<CareerPromiseCatalog>(json, SerializerOptions));
        }
        catch (Exception ex)
        {
            _logService.Warning($"경력 공약 저장본을 불러오지 못했습니다: {ex.Message}");
            return new CareerPromiseCatalog();
        }
    }

    private void SaveCatalog(string filePath, CareerPromiseCatalog catalog)
    {
        var directory = Path.GetDirectoryName(filePath);
        if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
        {
            Directory.CreateDirectory(directory);
        }

        var json = JsonSerializer.Serialize(catalog, SerializerOptions);
        var temporaryFilePath = $"{filePath}.tmp";
        File.WriteAllText(temporaryFilePath, json);
        File.Move(temporaryFilePath, filePath, overwrite: true);
    }

    private static string ResolveFilePath()
    {
        return Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "Tornado3_2026Election",
            "customer-data",
            "career-promises.json");
    }

    private static string ResolveLegacyFilePath()
    {
        return Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
            "Tornado3 Data",
            "CustomerData",
            "career-promises.json");
    }

    private void TryMigrateLegacyCatalog()
    {
        var legacyFilePath = ResolveLegacyFilePath();
        if (File.Exists(FilePath) || !File.Exists(legacyFilePath))
        {
            return;
        }

        try
        {
            var directory = Path.GetDirectoryName(FilePath);
            if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }

            File.Copy(legacyFilePath, FilePath, overwrite: false);
            _logService.Info($"기존 공약데이터 저장본을 새 위치로 복사했습니다: {FilePath}");
        }
        catch (Exception ex)
        {
            _logService.Warning($"기존 공약데이터 저장본 복사에 실패했습니다: {ex.Message}");
        }
    }

    private static CareerPromiseCatalog NormalizeCatalog(CareerPromiseCatalog? catalog)
    {
        if (catalog is null)
        {
            return new CareerPromiseCatalog();
        }

        return new CareerPromiseCatalog
        {
            Version = string.IsNullOrWhiteSpace(catalog.Version) ? "1" : catalog.Version,
            UpdatedAt = catalog.UpdatedAt ?? string.Empty,
            Entries = SafeEntries(catalog.Entries).ToArray()
        };
    }

    private static IEnumerable<CareerPromiseEntry> SafeEntries(IEnumerable<CareerPromiseEntry>? entries)
    {
        return entries?.Where(entry => entry is not null) ?? Array.Empty<CareerPromiseEntry>();
    }
}
