using AidA.Data;
using AidA.Data.Entities;
using DevExpress.Xpf.Editors;
using System;
using System.Collections.Generic;
using System.Linq;

namespace AidA.Service.Anonymization
{
    public class AnonymizationManager
    {
        private Dictionary<long, IList<Antrag>> AnsprechpartnerDictionary;

        private Dictionary<long, IList<Antrag>> AntragsPersonenDatenDictionary;

        private Dictionary<long, IList<Antrag>> PersonenIdentitaetDictionary;

        private IList<Antrag> ZuAnonymisierendeAntraege;

        private IList<Anonymisierung> Anonymisierungen;

        private IList<Anonymisierungsausnahme> Anonymisierungsausnahmen;

        private string UserName;


        public AnonymizationManager(String userName)
        {
            UserName = userName;
        }

        public Anonymisierungslauf AnonymisierungslaufVorbereiten(int aufbewahrungsfrist, ProgressBarEdit progressBar)
        {
            var antraege = Database.GetAll<Antrag>();
            int gepruefteAntraege = 0;
            int bereitsAnonymisierteAntraege = 0;
            int ausgenommeneAntraege = 0;
            int erledigteAntraege = 0;
            int nochAufzubewahrendeAntraege = 0;
            int antragsPersonenDatenCount = 0;
            int antragsGegenstaendeCount = 0;
            AntragsStatus antragsStatus;
            IList<AntragsPersonenDaten> antragsPersonenDatenListe;
            IList<AntragsGegenstand> antragsGegenstaende;
            DateTime? beginnAufbewahrungsfrist = null;
            DateTime aufbewahrenBis = new DateTime(DateTime.Now.Year - aufbewahrungsfrist, DateTime.Now.Month, DateTime.Now.Day);

            ZuAnonymisierendeAntraege = new List<Antrag>();
            AnsprechpartnerDictionary = new Dictionary<long, IList<Antrag>>();
            // AntragsPersonenDatenDictionary = new Dictionary<long, IList<Antrag>>();
            PersonenIdentitaetDictionary = new Dictionary<long, IList<Antrag>>();
            Anonymisierungen = Database.GetAll<Anonymisierung>();
            Anonymisierungsausnahmen = Database.GetAll<Anonymisierungsausnahme>();

            progressBar.Minimum = 0;
            progressBar.Maximum = antraege.Count;
            progressBar.Value = 0;
            progressBar.DisplayFormatString = progressBar.DisplayFormatString + " von " + antraege.Count;
            progressBar.Visibility = System.Windows.Visibility.Visible;

            Anonymisierungslauf anonymisierungslauf = new Anonymisierungslauf();
            anonymisierungslauf.PruefBeginn = DateTime.Now;
            anonymisierungslauf.PruefUser = UserName;
            anonymisierungslauf.Aufbewahrungsfrist = aufbewahrungsfrist;

            foreach (var antrag in antraege)
            {
                if (gepruefteAntraege % 1000 == 0)
                    System.Windows.Forms.Application.DoEvents();

                var anonymisierung = Anonymisierungen.FirstOrDefault(a => a.AntragsId.Equals(antrag.Id));
                if (anonymisierung != null)
                {
                    bereitsAnonymisierteAntraege++;
                    gepruefteAntraege++;
                    progressBar.Value++;
                    continue;
                }

                var anonymisierungsausnahme = Anonymisierungsausnahmen.FirstOrDefault(a => a.AntragsId.Equals(antrag.Id));
                if (anonymisierungsausnahme != null)
                {
                    ausgenommeneAntraege++;
                    UpdateAnsprechpartnerDictionary(antrag);
                    // UpdateAntragsPersonenDatenDictionary(antrag);
                    UpdatePersonenIdentitaetDictionary(antrag);
                    gepruefteAntraege++;
                    progressBar.Value++;
                    continue;
                }

                antragsStatus = antrag.AntragsStatus;

                if (antragsStatus != null && (antragsStatus.Name.Equals("erledigt") || antragsStatus.Name.Equals("mit Fehlern erledigt")))
                {
                    erledigteAntraege++;

                    antragsPersonenDatenListe = antrag.AntragsPersonenDatenListe;
                    foreach (var antragsPersonenDaten in antragsPersonenDatenListe)
                    {
                        antragsPersonenDatenCount++;

                        antragsGegenstaende = antragsPersonenDaten.AntragsGegenstaende;
                        DateTime? allMaxTS = null;
                        foreach (var antragsGegenstand in antragsGegenstaende)
                        {
                            antragsGegenstaendeCount++;
                            DateTime? maxTS = DateUtils.MaxTimestamp(antragsGegenstand.BeantragtTimestamp, antragsGegenstand.BewilligtTimestamp, antragsGegenstand.GeaendertDurchTimestamp);
                            allMaxTS = DateUtils.MaxTimestamp(allMaxTS, maxTS);
                        }

                        beginnAufbewahrungsfrist = allMaxTS;
                    }
                }

                // konnte fuer erledigte Antraege aufgrund fehlender Timestamps kein Beginn der Aufbewahrungsfrist ermittelt werden,
                // verfahren wir (bis auf Weiteres) wie bei nicht erledigten Antraegen
                if (beginnAufbewahrungsfrist is null)
                {
                    if (antrag.FaelligkeitsDatum != null)
                    {
                        DateTime faelligkeitsDatum = (DateTime)antrag.FaelligkeitsDatum;
                        beginnAufbewahrungsfrist = new DateTime(faelligkeitsDatum.Year + 1, 1, 1);
                    }
                    else
                    {
                        int bewilligungsJahr = antrag.BewilligungsJahr;
                        if (bewilligungsJahr == 0)
                        {
                            // es gibt 18 Antraege mit Bewilligungsjahr 0, haben alle Eingangsdatum > 2022
                            // wir setzen den Beginn der Aufbewahrungsfrist auf 2022, um die Anonymisierung zu verhindern
                            beginnAufbewahrungsfrist = new DateTime(2022, 1, 1);
                        }
                        else
                        {
                            beginnAufbewahrungsfrist = new DateTime(bewilligungsJahr + 1, 1, 1);
                        }
                    }
                }

                if (beginnAufbewahrungsfrist != null)
                {
                    DateTime beginn = (DateTime)beginnAufbewahrungsfrist;
                    DateTime endeAufbewahrungsfrist = new DateTime(beginn.Year + aufbewahrungsfrist, beginn.Month, beginn.Day);

                    if (DateTime.Now > endeAufbewahrungsfrist)
                    {
                        ZuAnonymisierendeAntraege.Add(antrag);
                    }
                    else
                    {
                        nochAufzubewahrendeAntraege++;
                    }
                }
                else
                {
                    nochAufzubewahrendeAntraege++;
                }

                UpdateAnsprechpartnerDictionary(antrag);
                // UpdateAntragsPersonenDatenDictionary(antrag);
                UpdatePersonenIdentitaetDictionary(antrag);
                gepruefteAntraege++;
                progressBar.Value++;
            }

            anonymisierungslauf.PruefEnde = DateTime.Now;
            anonymisierungslauf.AnzahlGepruefteAntraege = gepruefteAntraege;
            anonymisierungslauf.AnzahlBereitsAnonymisierteAntraege = bereitsAnonymisierteAntraege;
            anonymisierungslauf.AnzahlAusgenommeneAntraege = ausgenommeneAntraege;
            anonymisierungslauf.AnzahlNochAufzubewahrendeAntraege = nochAufzubewahrendeAntraege;
            anonymisierungslauf.AnzahlZuAnonymisierendeAntraege = ZuAnonymisierendeAntraege.Count;

            return anonymisierungslauf;
        }

        public void AnonymisierungslaufDurchfuehren(Anonymisierungslauf anonymisierungslauf, ProgressBarEdit progressBar)
        {
            Dictionary<long, PersonenIdentitaet> anonymisiertePersonenIdentitaeten = AnonymisiertePersonenIdentitaetenVorbereiten();

            anonymisierungslauf.AnonymisierungsBeginn = DateTime.Now;
            anonymisierungslauf.AnonymisierungsUser = UserName;
            anonymisierungslauf.Sicherung = "Anonymisierungsbackup_" + String.Format("{0:yyyyMMdd HHmm}", anonymisierungslauf.AnonymisierungsBeginn) + ".bak";

            Database.InsertAnonymisierungslauf(anonymisierungslauf, UserName);

            anonymisierungslauf.Anonymisierungen = new List<Anonymisierung>();
            int anzahlAnonymisierteAntraege = 0;
            var ansprechpartnerIds = new List<long>();

            // long antragsIdFuerAntragspersondatenEntknuepfung = Properties.Settings.Default.AntragsIdFuerAntragspersondatenEntknuepfung;

            progressBar.Minimum = 0;
            progressBar.Maximum = ZuAnonymisierendeAntraege.Count;
            progressBar.Value = 0;
            progressBar.DisplayFormatString = progressBar.DisplayFormatString + " von " + ZuAnonymisierendeAntraege.Count;
            progressBar.Visibility = System.Windows.Visibility.Visible;

            foreach (var antrag in ZuAnonymisierendeAntraege)
            {
                if (anzahlAnonymisierteAntraege % 20 == 0)
                    System.Windows.Forms.Application.DoEvents();

                Anonymisierung anonymisierung = new Anonymisierung();
                anonymisierung.AntragsId = antrag.Id;
                anonymisierung.Aufbewahrungsfrist = anonymisierungslauf.Aufbewahrungsfrist;
                anonymisierung.AntragsNummer = antrag.AntragsNummer;
                anonymisierung.AnonymisierungslaufId = anonymisierungslauf.Id;

                // Beratungsstellenansprechpartner
                Ansprechpartner ansprechpartner = antrag.Ansprechpartner;
                if (ansprechpartner != null)
                {
                    bool ansprechpartnerNochBenoetigt = CheckAnsprechpartnerNochBenoetigt(ansprechpartner, antrag);
                    if (!ansprechpartnerNochBenoetigt)
                    {
                        // Beratungsstelle wird zurzeit noch nicht benoetigt
                        // Beratungsstelle beratungsstelle = ansprechpartner.Beratungsstelle;

                        if (ansprechpartner.KontaktInfo != null)
                        {
                            ansprechpartner.KontaktInfo = null;
                        }

                        // Ansprechpartner (und Antrag.AnsprechpartnerId) loeschen (todo: Pruefen, ob das so klappt)
                        antrag.Ansprechpartner = null;
                    }
                    else
                    {
                        // Ansprechpartner entknuepfen, auch wenn er von anderen Antraegen noch benoetigt wird
                        antrag.AnsprechpartnerId = null;
                    }
                }

                antrag.SachbearbeiterId = null;

                IList<AntragsPersonenDaten> antragsPersonenDatenListe = antrag.AntragsPersonenDatenListe;
                if (antragsPersonenDatenListe != null)
                {
                    foreach (var antragsPersonenDaten in antragsPersonenDatenListe)
                    {
                        antragsPersonenDaten.Strasse = null;
                        if (antragsPersonenDaten.Plz != null && antragsPersonenDaten.Plz.Length > 2)
                        {
                            antragsPersonenDaten.Plz = antragsPersonenDaten.Plz.Substring(0, 2);
                        }
                        antragsPersonenDaten.Ort = null;
                        antragsPersonenDaten.Handy = null;
                        antragsPersonenDaten.Telefon = null;
                        antragsPersonenDaten.Fax = null;
                        antragsPersonenDaten.Email = null;
                        antragsPersonenDaten.Iban = null;
                        antragsPersonenDaten.Bank = null;

                        PersonenIdentitaet personenIdentitaet = antragsPersonenDaten.PersonenIdentitaet;
                        bool nochBenoetigt = CheckPersonenIdentitaetNochBenoetigt(personenIdentitaet, antrag);
                        if (nochBenoetigt)
                        {
                            antragsPersonenDaten.PersonenIdentitaet = anonymisiertePersonenIdentitaeten[antragsPersonenDaten.Id];
                        }
                        else
                        {
                            personenIdentitaet.Vorname = "Anonymisierte";
                            personenIdentitaet.Nachname = "Person";
                            DateTime? geburtsdatum = personenIdentitaet.Geburtsdatum;
                            if (geburtsdatum != null)
                            {
                                personenIdentitaet.Geburtsdatum = new DateTime(((DateTime)geburtsdatum).Year, 1, 1);
                            }
                        }

                        IList<AntragsGegenstand> antragsGegenstaende = antragsPersonenDaten.AntragsGegenstaende;
                        foreach (var antragsGegenstand in antragsGegenstaende)
                        {
                            IList<FinanzTransaktion> finanzTransaktionen = antragsGegenstand.FinanzTransaktionen;
                            foreach (var finanzTransaktion in finanzTransaktionen)
                            {
                                IList<ZahlungSEPA> zahlungenSEPA = finanzTransaktion.ZahlungenSEPA;
                                foreach (var zahlungSEPA in zahlungenSEPA)
                                {
                                    zahlungSEPA.Empfaenger = "";
                                    zahlungSEPA.IBAN = "";
                                    zahlungSEPA.BIC = "";
                                    zahlungSEPA.Verwendungszweck = "";
                                }
                            }
                        }
                    }
                }

                // Memo loeschen - noch pruefen ob so gueltig und verarbeitbar
                antrag.Memo = null;

                anonymisierungslauf.Anonymisierungen.Add(anonymisierung);
                anzahlAnonymisierteAntraege++;
                progressBar.Value++;
            }

            anonymisierungslauf.AnzahlAnonymisierteAntraege = anzahlAnonymisierteAntraege;
            anonymisierungslauf.AnonymisierungsEnde = DateTime.Now;

            Database.SaveAnonymisierungslaufDurchfuehrung(anonymisierungslauf, ZuAnonymisierendeAntraege, UserName);
        }

        public void AnonymisierungslaufSpeichern(Anonymisierungslauf anonymisierungslauf)
        {
            Database.UpdateAnonymisierungslauf(anonymisierungslauf, UserName);
        }

        public void AnonymisierungsAusnahmeHinzufuegen(long antragsNummer)
        {
            Anonymisierungsausnahme anonymisierungsausnahme = new Anonymisierungsausnahme();
            anonymisierungsausnahme.AntragsNummer = antragsNummer;
            anonymisierungsausnahme.AntragsId = Database.GetAntragsIdForAntragsNummer(antragsNummer);

            Database.InsertAnonymisierungsausnahme(anonymisierungsausnahme, UserName);
        }

        public void AnonymisierungsAusnahmeLoeschen(Anonymisierungsausnahme anonymisierungsausnahme)
        {
            Database.DeleteAnonymisierungsausnahme(anonymisierungsausnahme);
        }

        private void UpdateAnsprechpartnerDictionary(Antrag antrag)
        {
            Ansprechpartner ansprechpartner = antrag.Ansprechpartner;
            if (ansprechpartner != null)
            {
                if (AnsprechpartnerDictionary.ContainsKey(ansprechpartner.Id))
                {
                    AnsprechpartnerDictionary[ansprechpartner.Id].Add(antrag);
                }
                else
                {
                    AnsprechpartnerDictionary.Add(ansprechpartner.Id, new List<Antrag> { antrag });
                }
            }
        }

        private void UpdateAntragsPersonenDatenDictionary(Antrag antrag)
        {
            IList<AntragsPersonenDaten> antragsPersonenDatenListe = antrag.AntragsPersonenDatenListe;
            if (antragsPersonenDatenListe != null)
            {
                foreach (var antragsPersonenDaten in antragsPersonenDatenListe)
                {
                    if (AntragsPersonenDatenDictionary.ContainsKey(antragsPersonenDaten.Id))
                    {
                        AntragsPersonenDatenDictionary[antragsPersonenDaten.Id].Add(antrag);
                    }
                    else
                    {
                        AntragsPersonenDatenDictionary.Add(antragsPersonenDaten.Id, new List<Antrag> { antrag });
                    }
                }
            }
        }

        private void UpdatePersonenIdentitaetDictionary(Antrag antrag)
        {
            IList<AntragsPersonenDaten> antragsPersonenDatenListe = antrag.AntragsPersonenDatenListe;
            if (antragsPersonenDatenListe != null)
            {
                foreach (var antragsPersonenDaten in antragsPersonenDatenListe)
                {
                    PersonenIdentitaet personenIdentitaet = antragsPersonenDaten.PersonenIdentitaet;
                    if (personenIdentitaet != null)
                    {
                        if (PersonenIdentitaetDictionary.ContainsKey(personenIdentitaet.Id))
                        {
                            PersonenIdentitaetDictionary[personenIdentitaet.Id].Add(antrag);
                        }
                        else
                        {
                            PersonenIdentitaetDictionary.Add(personenIdentitaet.Id, new List<Antrag> { antrag });
                        }
                    }
                }
            }
        }

        /**
         * Prueft, ob der Ansprechpartner eines Antrags noch fuer andere Antraege benoetigt wird
         */
        private bool CheckAnsprechpartnerNochBenoetigt(Ansprechpartner ansprechpartner, Antrag antrag)
        {
            bool nochBenoetigt = false;
            if (AnsprechpartnerDictionary.ContainsKey(ansprechpartner.Id))
            {
                var ansprechpartnerAntraege = AnsprechpartnerDictionary[ansprechpartner.Id];

                if (ansprechpartnerAntraege != null && ansprechpartnerAntraege.Count > 1)
                {
                    foreach (var ansprechpartnerAntrag in ansprechpartnerAntraege)
                    {
                        if (ansprechpartnerAntrag.Id != antrag.Id)
                        {
                            var zuAnonymisieren = ZuAnonymisierendeAntraege.FirstOrDefault(a => a.Id.Equals(ansprechpartnerAntrag.Id));
                            if (zuAnonymisieren is null)
                            {
                                var schonAnonymisiert = Anonymisierungen.FirstOrDefault(a => a.AntragsId.Equals(ansprechpartnerAntrag.Id));
                                if (schonAnonymisiert is null)
                                {
                                    nochBenoetigt = true;
                                }
                            }
                        }
                    }
                }
            }
            return nochBenoetigt;
        }

        /**
         * Prueft, ob AntragsPersonenDaten eines Antrags noch fuer andere Antraege benoetigt werden
         */
        private bool CheckAntragsPersonenDatenNochBenoetigt(AntragsPersonenDaten antragsPersonenDaten, Antrag antrag)
        {
            bool nochBenoetigt = false;
            if (AntragsPersonenDatenDictionary.ContainsKey(antragsPersonenDaten.Id))
            {
                var antragsPersonenDatenAntraege = AntragsPersonenDatenDictionary[antragsPersonenDaten.Id];

                if (antragsPersonenDatenAntraege != null && antragsPersonenDatenAntraege.Count > 1)
                {
                    foreach (var antragsPersonenDatenAntrag in antragsPersonenDatenAntraege)
                    {
                        if (antragsPersonenDatenAntrag.Id != antrag.Id)
                        {
                            var zuAnonymisieren = ZuAnonymisierendeAntraege.FirstOrDefault(a => a.Id.Equals(antragsPersonenDatenAntrag.Id));
                            if (zuAnonymisieren is null)
                            {
                                var schonAnonymisiert = Anonymisierungen.FirstOrDefault(a => a.AntragsId.Equals(antragsPersonenDatenAntrag.Id));
                                if (schonAnonymisiert is null)
                                {
                                    nochBenoetigt = true;
                                }
                            }
                        }
                    }
                }
            }
            return nochBenoetigt;
        }

        /**
         * Prueft, ob eine PersonenIdentitaet eines Antrags noch fuer andere Antraege benoetigt wird
         */
        private bool CheckPersonenIdentitaetNochBenoetigt(PersonenIdentitaet personenIdentitaet, Antrag antrag)
        {
            bool nochBenoetigt = false;
            if (PersonenIdentitaetDictionary.ContainsKey(personenIdentitaet.Id))
            {
                var personenIdentitaetAntraege = PersonenIdentitaetDictionary[personenIdentitaet.Id];

                if (personenIdentitaetAntraege != null && personenIdentitaetAntraege.Count > 1)
                {
                    foreach (var personenIdentitaetAntrag in personenIdentitaetAntraege)
                    {
                        if (personenIdentitaetAntrag.Id != antrag.Id)
                        {
                            var zuAnonymisieren = ZuAnonymisierendeAntraege.FirstOrDefault(a => a.Id.Equals(personenIdentitaetAntrag.Id));
                            if (zuAnonymisieren is null)
                            {
                                var schonAnonymisiert = Anonymisierungen.FirstOrDefault(a => a.AntragsId.Equals(personenIdentitaetAntrag.Id));
                                if (schonAnonymisiert is null)
                                {
                                    nochBenoetigt = true;
                                }
                            }
                        }
                    }
                }
            }
            return nochBenoetigt;
        }

        /**
         * Bereitet fuer die zu anonymisierenden Antraege anonymisierte PersonenIdentitaeten vor.
         * Sucht erst in der Datenbank. Nicht vorhandene Eintraege werden erzeugt und in der Datenbank gespeichert.
         */
        private Dictionary<long, PersonenIdentitaet> AnonymisiertePersonenIdentitaetenVorbereiten()
        {
            string vorname = "Anonymisierte";
            string nachname = "Personenidentität";
            IList<PersonenIdentitaet> dbAnonymisiertePersonen = Database.GetPersonenIdentitaeten(vorname, nachname);
            Dictionary<DateTime, PersonenIdentitaet> neuAnonymisiertePersonen = new Dictionary<DateTime, PersonenIdentitaet>();
            Dictionary<long, PersonenIdentitaet> anonymisiertePersonenFuerAntragsPersonenDaten = new Dictionary<long, PersonenIdentitaet>();
            DateTime nullDatum = new DateTime(42, 1, 1);

            foreach (var antrag in ZuAnonymisierendeAntraege)
            {
                IList<AntragsPersonenDaten> antragsPersonenDatenListe = antrag.AntragsPersonenDatenListe;
                if (antragsPersonenDatenListe != null)
                {
                    foreach (var antragsPersonenDaten in antragsPersonenDatenListe)
                    {
                        PersonenIdentitaet anonymisiertePersonenIdentitaet = null;
                        DateTime anonymisiertesDatum;

                        DateTime? geburtsdatum = antragsPersonenDaten.PersonenIdentitaet.Geburtsdatum;
                        if (geburtsdatum != null)
                        {
                            anonymisiertesDatum = new DateTime(((DateTime)geburtsdatum).Year, 1, 1);
                        }
                        else
                        {
                            // bei Geburtsdatum null den 1.1.0042 zum Zwischenspeichern verwenden
                            anonymisiertesDatum = nullDatum;
                        }

                        foreach (var pi in dbAnonymisiertePersonen.Where(p => p.Geburtsdatum == anonymisiertesDatum))
                        {
                            // es kann nur eine wie hier anonymisierte Person pro Jahrgang geben
                            anonymisiertePersonenIdentitaet = pi;
                        }

                        if (anonymisiertePersonenIdentitaet is null)
                        {
                            if (neuAnonymisiertePersonen.ContainsKey(anonymisiertesDatum))
                            {
                                anonymisiertePersonenIdentitaet = neuAnonymisiertePersonen[anonymisiertesDatum];
                            }
                            else
                            {
                                anonymisiertePersonenIdentitaet = new PersonenIdentitaet();
                                anonymisiertePersonenIdentitaet.Vorname = vorname;
                                anonymisiertePersonenIdentitaet.Nachname = nachname;
                                anonymisiertePersonenIdentitaet.Version = 1;
                                if (anonymisiertesDatum != nullDatum)
                                    anonymisiertePersonenIdentitaet.Geburtsdatum = anonymisiertesDatum;

                                Database.InsertPersonenIdentitaet(anonymisiertePersonenIdentitaet);
                                neuAnonymisiertePersonen.Add(anonymisiertesDatum, anonymisiertePersonenIdentitaet);
                            }
                        }

                        anonymisiertePersonenFuerAntragsPersonenDaten.Add(antragsPersonenDaten.Id, anonymisiertePersonenIdentitaet);
                    }
                }
            }

            return anonymisiertePersonenFuerAntragsPersonenDaten;
        }
    }
}
