using Elwig.Models;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Threading.Tasks;

namespace Elwig.Helpers.Export {
    public class Ebics(PaymentVar variant, string filename, int version, Ebics.AddressMode mode = Ebics.AddressMode.Full) : IBankingExporter {

        public enum AddressMode { Omit = 0, Lines = 1, Full = 2 }

        public static string FileExtension => "xml";

        private readonly StreamWriter Writer = new(filename, false, Utils.UTF8);
        private readonly DateOnly Date = variant.TransferDate ?? throw new ArgumentException("TransferDate has to be set in PaymentVar");
        private readonly int Year = variant.Year;
        private readonly string Name = variant.Name;
        private readonly int AvNr = variant.AvNr;
        private readonly int Version = version;
        private readonly AddressMode ShowAddresses = mode;

        public void Dispose() {
            GC.SuppressFinalize(this);
            Writer.Dispose();
        }

        public ValueTask DisposeAsync() {
            GC.SuppressFinalize(this);
            return Writer.DisposeAsync();
        }

        public void Export(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
            ExportAsync(transactions, progress).GetAwaiter().GetResult();
        }

        public async Task ExportAsync(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
            if (transactions.Any(tx => tx.Amount < 0)) {
                throw new ArgumentException("Tranaction amount may not be negative");
            } else if (App.Client.Iban == null) {
                throw new ArgumentException("Client IBAN has to be set");
            }
            progress?.Report(0.0);
            var nbOfTxs = transactions.Count();
            int count = nbOfTxs + 2, i = 0;
            var ctrlSum = transactions.Sum(tx => tx.Amount);
            var msgId = $"ELWIG-{App.Client.NameToken}-{Year}-AV{AvNr:00}";
            var pmtInfId = $"{msgId}-1";

            await Writer.WriteLineAsync($"""
                <?xml version="1.0" encoding="UTF-8"?>
                <Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.{Version:00}">
                 <CstmrCdtTrfInitn>
                  <GrpHdr>
                   <MsgId>{msgId}</MsgId>
                   <CreDtTm>{DateTime.UtcNow:o}</CreDtTm>
                   <NbOfTxs>{nbOfTxs}</NbOfTxs>
                   <CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum>
                   <InitgPty><Nm>{SecurityElement.Escape(App.Client.NameFull)}</Nm></InitgPty>
                  </GrpHdr>
                  <PmtInf>
                   <PmtInfId>{pmtInfId}</PmtInfId>
                   <PmtMtd>TRF</PmtMtd>
                   <NbOfTxs>{nbOfTxs}</NbOfTxs>
                   <CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum>
                   <ReqdExctnDt>{(Version >= 8 ? "<Dt>" : "")}{Date:yyyy-MM-dd}{(Version >= 8 ? "</Dt>" : "")}</ReqdExctnDt>
                   <Dbtr><Nm>{SecurityElement.Escape(App.Client.NameFull)}</Nm></Dbtr>
                   <DbtrAcct><Id><IBAN>{App.Client.Iban!.Replace(" ", "")}</IBAN></Id></DbtrAcct>
                   <DbtrAgt><FinInstnId>{(Version >= 4 ? "<BICFI>" : "<BIC>")}{App.Client.Bic ?? "NOTPROVIDED"}{(Version >= 4 ? "</BICFI>" : "</BIC>")}</FinInstnId></DbtrAgt>
                """);
            progress?.Report(100.0 * ++i / count);

            foreach (var tx in transactions) {
                var a = (IAddress?)tx.Member.BillingAddress ?? tx.Member;
                var (a1, a2) = Utils.SplitAddress(a.Address);
                var id = $"ELWIG-{App.Client.NameToken}-{Year}-TG{tx.Nr:0000}";
                var info = $"{Name} - Traubengutschrift Nr. {Year}/{tx.Nr:000}";
                await Writer.WriteLineAsync($"""
                       <CdtTrfTxInf>
                        <PmtId><EndToEndId>{id}</EndToEndId></PmtId>
                        <Amt><InstdAmt Ccy="{tx.Currency}">{Transaction.FormatAmount(tx.Amount)}</InstdAmt></Amt>
                        <Cdtr>
                         <Nm>{SecurityElement.Escape(a.FullName[..Math.Min(140, a.FullName.Length)])}</Nm>
                    """);
                if (ShowAddresses != AddressMode.Omit) {
                    var full = ShowAddresses == AddressMode.Full;
                    await Writer.WriteLineAsync($"""
                             <PstlAdr>{(full ? "" : $"\r\n      <Ctry>{a.PostalDest.Country.Alpha2}</Ctry>")}
                              {(full ? $"<StrtNm>{SecurityElement.Escape(a1?[..Math.Min(70, a1.Length)])}</StrtNm> <BldgNb>{SecurityElement.Escape(a2?[..Math.Min(16, a2.Length)])}</BldgNb>" :
                                       $"<AdrLine>{SecurityElement.Escape(a.Address[..Math.Min(70, a.Address.Length)])}</AdrLine>")}
                              <{(full ? "PstCd" : "AdrLine")}>{a.PostalDest.AtPlz?.Plz}{(full ? "</PstCd> <TwnNm>" : " ")}{SecurityElement.Escape(a.PostalDest.AtPlz?.Ort.Name)}</{(full ? "TwnNm" : "AdrLine")}>
                             {(full ? $" <Ctry>{a.PostalDest.Country.Alpha2}</Ctry>\r\n     " : "")}</PstlAdr>
                        """);
                }
                await Writer.WriteLineAsync($"""
                        </Cdtr>
                        <CdtrAcct><Id><IBAN>{tx.Member.Iban!}</IBAN></Id></CdtrAcct>
                        <RmtInf><Ustrd>{SecurityElement.Escape(info)}</Ustrd></RmtInf>
                       </CdtTrfTxInf>
                    """);
                progress?.Report(100.0 * ++i / count);
            }

            await Writer.WriteLineAsync("""
                  </PmtInf>
                 </CstmrCdtTrfInitn>
                </Document>
                """);
            await Writer.FlushAsync();
            progress?.Report(100.0);
        }
    }
}
