//-----------------------------------------------------------------------
// <copyright file="ItemCsvService.cs" company="aliasvault">
// Copyright (c) aliasvault. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------

namespace AliasVault.ImportExport;

using AliasClientDb;
using AliasClientDb.Models;
using AliasVault.ImportExport.Importers;
using AliasVault.ImportExport.Models;
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;

/// <summary>
/// Exports and imports Item objects to and from CSV files.
/// </summary>
public static class ItemCsvService
{
    /// <summary>
    /// Export list of items to CSV file.
    /// </summary>
    /// <param name="items">List of items to export.</param>
    /// <returns>CSV file as byte array.</returns>
    public static byte[] ExportItemsToCsv(List<Item> items)
    {
        var records = new List<ItemCsvRecord>();

        foreach (var item in items)
        {
            var record = new ItemCsvRecord
            {
                ServiceName = item.Name ?? string.Empty,
                FolderPath = BuildFolderPath(item.Folder),
                ServiceUrl = GetFieldValue(item, FieldKey.LoginUrl),
                Username = GetFieldValue(item, FieldKey.LoginUsername),
                CurrentPassword = GetFieldValue(item, FieldKey.LoginPassword),
                AliasEmail = GetFieldValue(item, FieldKey.LoginEmail),
                TwoFactorSecret = item.TotpCodes.FirstOrDefault(t => !t.IsDeleted)?.SecretKey ?? string.Empty,
                AliasGender = GetFieldValue(item, FieldKey.AliasGender),
                AliasFirstName = GetFieldValue(item, FieldKey.AliasFirstName),
                AliasLastName = GetFieldValue(item, FieldKey.AliasLastName),
                AliasNickName = string.Empty, // NickName is no longer stored as a separate field
                AliasBirthDate = ParseBirthDate(GetFieldValue(item, FieldKey.AliasBirthdate)),
                Notes = GetFieldValue(item, FieldKey.NotesContent),
                CreatedAt = item.CreatedAt,
                UpdatedAt = item.UpdatedAt,
                CardholderName = GetFieldValue(item, FieldKey.CardCardholderName),
                CardNumber = GetFieldValue(item, FieldKey.CardNumber),
                CardExpiryMonth = GetFieldValue(item, FieldKey.CardExpiryMonth),
                CardExpiryYear = GetFieldValue(item, FieldKey.CardExpiryYear),
                CardCvv = GetFieldValue(item, FieldKey.CardCvv),
                CardPin = GetFieldValue(item, FieldKey.CardPin),
            };

            records.Add(record);
        }

        using var memoryStream = new MemoryStream();
        using var writer = new StreamWriter(memoryStream);
        using var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture));

        csv.WriteRecords(records);
        writer.Flush();
        return memoryStream.ToArray();
    }

    /// <summary>
    /// Imports Item objects from a CSV file.
    /// </summary>
    /// <param name="fileContent">The content of the CSV file.</param>
    /// <returns>The imported list of ImportedCredential objects.</returns>
    public static async Task<List<ImportedCredential>> ImportItemsFromCsv(string fileContent)
    {
        using var reader = new StringReader(fileContent);
        var config = new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            // Allow missing headers - fields that don't exist will be skipped
            HeaderValidated = null,
            MissingFieldFound = null,
        };
        using var csv = new CsvReader(reader, config);

        var records = new List<ItemCsvRecord>();
        await foreach (var record in csv.GetRecordsAsync<ItemCsvRecord>())
        {
            records.Add(record);
        }

        if (records.Count == 0)
        {
            throw new InvalidOperationException("No records found in the CSV file.");
        }

        var credentials = new List<ImportedCredential>();

        foreach (var record in records)
        {
            var credential = new ImportedCredential
            {
                ServiceName = record.ServiceName,
                ServiceUrls = BaseImporter.ParseUrls(record.ServiceUrl),
                Username = record.Username,
                Password = record.CurrentPassword,
                Email = record.AliasEmail,
                Notes = record.Notes,
                Alias = new ImportedAlias
                {
                    Gender = record.AliasGender,
                    FirstName = record.AliasFirstName,
                    LastName = record.AliasLastName,
                    NickName = record.AliasNickName,
                    BirthDate = record.AliasBirthDate,
                    CreatedAt = record.CreatedAt,
                    UpdatedAt = record.UpdatedAt,
                },
                TwoFactorSecret = record.TwoFactorSecret,
                CreatedAt = record.CreatedAt,
                UpdatedAt = record.UpdatedAt,
                FolderPath = string.IsNullOrWhiteSpace(record.FolderPath) ? null : record.FolderPath,
            };

            if (HasCardData(record))
            {
                credential.ItemType = ImportedItemType.Creditcard;
                credential.Creditcard = new ImportedCreditcard
                {
                    CardholderName = record.CardholderName,
                    Number = record.CardNumber,
                    ExpiryMonth = record.CardExpiryMonth,
                    ExpiryYear = record.CardExpiryYear,
                    Cvv = record.CardCvv,
                    Pin = record.CardPin,
                };
            }

            credentials.Add(credential);
        }

        return credentials;
    }

    /// <summary>
    /// Builds the full hierarchical path for a folder by traversing parent folders.
    /// </summary>
    /// <param name="folder">The folder to build the path for.</param>
    /// <returns>The full hierarchical path (e.g., "Work/Projects/Active"), or empty string if null.</returns>
    private static string BuildFolderPath(Folder? folder)
    {
        if (folder == null)
        {
            return string.Empty;
        }

        var pathParts = new List<string>();
        var currentFolder = folder;

        // Traverse up the folder hierarchy
        while (currentFolder != null)
        {
            pathParts.Insert(0, currentFolder.Name);
            currentFolder = currentFolder.ParentFolder;
        }

        return string.Join("/", pathParts);
    }

    /// <summary>
    /// Gets a field value from an item by field key.
    /// </summary>
    /// <param name="item">The item to get the field value from.</param>
    /// <param name="fieldKey">The field key to look up.</param>
    /// <returns>The field value, or empty string if not found.</returns>
    private static string GetFieldValue(Item item, string fieldKey)
    {
        return item.FieldValues
            .FirstOrDefault(fv => fv.FieldKey == fieldKey && !fv.IsDeleted)
            ?.Value ?? string.Empty;
    }

    /// <summary>
    /// Returns true if the CSV record has any credit card field populated.
    /// </summary>
    /// <param name="record">The CSV record to inspect.</param>
    /// <returns>True if any card field has a non-empty value.</returns>
    private static bool HasCardData(ItemCsvRecord record)
    {
        return !string.IsNullOrWhiteSpace(record.CardholderName)
            || !string.IsNullOrWhiteSpace(record.CardNumber)
            || !string.IsNullOrWhiteSpace(record.CardExpiryMonth)
            || !string.IsNullOrWhiteSpace(record.CardExpiryYear)
            || !string.IsNullOrWhiteSpace(record.CardCvv)
            || !string.IsNullOrWhiteSpace(record.CardPin);
    }

    /// <summary>
    /// Parses a birth date string to a DateTime.
    /// </summary>
    /// <param name="birthDateStr">The birth date string in yyyy-MM-dd format.</param>
    /// <returns>The parsed DateTime, or null if the string is empty or invalid.</returns>
    private static DateTime? ParseBirthDate(string birthDateStr)
    {
        if (string.IsNullOrEmpty(birthDateStr))
        {
            return null;
        }

        if (DateTime.TryParseExact(birthDateStr, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date))
        {
            return date;
        }

        return null;
    }
}

/// <summary>
/// CSV record for Item objects.
/// </summary>
public class ItemCsvRecord
{
    /// <summary>
    /// Gets or sets the service name.
    /// </summary>
    public string ServiceName { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the folder path.
    /// Added in version 1.7.0.
    /// </summary>
    public string FolderPath { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the service URL.
    /// </summary>
    public string ServiceUrl { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the username.
    /// </summary>
    public string Username { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the current password.
    /// </summary>
    public string CurrentPassword { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the alias email.
    /// </summary>
    public string AliasEmail { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the two-factor secret.
    /// </summary>
    public string TwoFactorSecret { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the alias gender.
    /// </summary>
    public string AliasGender { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the alias first name.
    /// </summary>
    public string AliasFirstName { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the alias last name.
    /// </summary>
    public string AliasLastName { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the alias nickname (legacy field, no longer used).
    /// </summary>
    public string AliasNickName { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the alias birth date.
    /// </summary>
    public DateTime? AliasBirthDate { get; set; } = null;

    /// <summary>
    /// Gets or sets the credit card cardholder name.
    /// Empty for non-credit-card items.
    /// </summary>
    public string CardholderName { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the credit card number.
    /// Empty for non-credit-card items.
    /// </summary>
    public string CardNumber { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the credit card expiry month (01-12).
    /// Empty for non-credit-card items.
    /// </summary>
    public string CardExpiryMonth { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the credit card expiry year (e.g., "2028").
    /// Empty for non-credit-card items.
    /// </summary>
    public string CardExpiryYear { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the credit card CVV.
    /// Empty for non-credit-card items.
    /// </summary>
    public string CardCvv { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the credit card PIN.
    /// Empty for non-credit-card items.
    /// </summary>
    public string CardPin { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the notes.
    /// </summary>
    public string Notes { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the created timestamp.
    /// </summary>
    public DateTime CreatedAt { get; set; } = DateTime.MinValue;

    /// <summary>
    /// Gets or sets the updated timestamp.
    /// </summary>
    public DateTime UpdatedAt { get; set; } = DateTime.MinValue;
}
