using CalculationBackend.Model;
using CalculationBackend.Model.Enums;
using CalculationBackend.Model.ImportExport;
using CalculationBackend.Model.Tables;
using HotChocolate;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace CalculationBackend.Controller.Helpers
{
    public class SIA451Reader
    {
        public static IEnumerable<IEnumerable<SiaRecord>> ParseRecordsFromCrbxFile(string crbxFilePath)
        {
            string fileExtension = System.IO.Path.GetExtension(crbxFilePath);

            if (fileExtension.ToLower() != ".crbx")
            {
                throw new FileNotFoundException("Die Datei ist keine .crbx Datei.");
            }
            if (!File.Exists(crbxFilePath))
            {
                throw new FileNotFoundException("Die .crbx Datei wurde nicht gefunden.");
            }

            // Hier sind mögliche Dateiendungen
            var validExtensions = new HashSet<string> { ".01s", ".e1s" };
            
            List<Stream> fileStreams = new List<Stream>();
            var archive = ZipFile.OpenRead(crbxFilePath);
            foreach (ZipArchiveEntry entry in archive.Entries)
            {
                if (validExtensions.Any(ext => entry.FullName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
                {
                    var stream = entry.Open();
                    fileStreams.Add(stream);
                }
            }
            var records = fileStreams.Select(ParseRecordsFromStream).ToList();
            archive.Dispose();
            return records;
        }
        
        public static IEnumerable<SiaRecord> ParseRecordsFromStream(Stream fileStream)
        {
            var lines = new List<string>();

            using var reader = new StreamReader(fileStream);
            while (!reader.EndOfStream)
            {
                lines.Add(reader.ReadLine());
            }

            var records = lines.Select(line =>
            {
                if (line.StartsWith("B"))
                    return new SiaRecordB(line);
                if (line.StartsWith("G"))
                    return new SiaRecordG(line);
                if (line.StartsWith("A"))
                    return new SiaRecordHeader(line);
                if (line.StartsWith("Z"))
                    return new SiaRecordFooter(line);
                return new SiaRecord(line);
            });
            return records;
        }

        public static void ReadSIA451File([Service] TableContext dbContext, [Service] ViewContext viewContext, Guid calculationId, Stream fileStream)
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

            var vofList    = new List<ValueOrFormula>();
            var calCatList = new List<CalculationCategory>();

            var records = new List<SiaRecord>();
            try
            {
                records = ParseRecordsFromStream(fileStream).ToList();
            }
            catch (Exception ex)
            {
                GeneralHelper.ThrowException($"Fehler beim Auslesen der Datei", ex.Message);
            }

            try
            {
                var calculation = GeneralHelper.GetActualCalculation(dbContext, calculationId);
                int sortOrder = calculation.ActualCalculationCategories
                                           .Where(a => a.Calculationid == calculationId && a.deleted != true)
                                           .Count() + 1;

                var project = viewContext.Projects.FirstOrDefault(a => a.projectId == calculation.ActualCalculation.ProjID && a.projectTyp == calculation.ActualCalculation.ProjTyp);

                var kagCodeToMappingId = CreateKagCodeToMappingIdMap(dbContext, records.OfType<SiaRecordB>(), calculationId, project.sparteId);

                records
                .OfType<SiaRecordG>()
                .GroupBy(l2 => new { l2.Kapitelnummer, l2.Leistungsposition }, (key, recs) => new SiaRecordGGroup(recs.ToList()))
                .GroupBy(l1 => new { l1.Kapitelnummer })
                .ToList()
                .ForEach(l1 =>
                {
                    var currentLevelOneId = Guid.NewGuid();

                    var vofL1    = CreateValueOrFormula(calculationId, null);

                    var calCatL1 = new CalculationCategory()
                    {
                        id                          = currentLevelOneId,
                        Calculationid               = calculationId,
                        parentCalculationCategoryid = null,
                        label                       = l1.Key.Kapitelnummer,
                        amountid                    = vofL1.id,
                        name                        = getL1Name(records, l1.Key.Kapitelnummer),
                        sortOrder                   = sortOrder,
                        inclusionOption             = InclusionOption.INCLUDED,
                        RefCalculationId            = calculationId,
                        description                 = l1.FirstOrDefault().Positionstext,
                        unit                        = Unit.NONE,
                        cost                        = null,
                        mappingid                   = null
                    };

                    vofList.Add(vofL1);
                    calCatList.Add(calCatL1);
                    sortOrder++;

                    l1.ToList().ForEach(l2 =>
                    {
                        if (l2.Leistungsposition == null) return;

                        if (l2.Unterpositionen.Count == 0)
                        {
                            var currentLevelTwoId = Guid.NewGuid();

                            string name = (l2.Überschrift ?? string.Empty).TrimEnd();

                            var label = l2.Leistungsposition.Insert(3, ".").TrimEnd();

                            var vofL3 = CreateValueOrFormula(calculationId, null);

                            var calCatL3 = new CalculationCategory()
                            {
                                id = currentLevelTwoId,
                                Calculationid = null,
                                parentCalculationCategoryid = currentLevelOneId,
                                label = label,
                                amountid = vofL3.id,
                                name = name,
                                sortOrder = sortOrder,
                                inclusionOption = InclusionOption.INCLUDED,
                                RefCalculationId = calculationId,
                                description = l2.Positionstext,
                                unit = GetUnitFromString(l2.Mengeneinheit),
                                cost = null,
                            };

                            vofList.Add(vofL3);
                            calCatList.Add(calCatL3);
                            sortOrder++;
                        }
                        else {
                            l2.Unterpositionen.ForEach(l3 =>
                            {
                                var currentLevelTwoId = Guid.NewGuid();

                                var name = GenerateL3Name(l2, l3);

                                var label = l2.Leistungsposition.Insert(3, ".").TrimEnd();

                                var vofL3 = CreateValueOrFormula(calculationId, l3.Menge);

                                var calCatL3 = new CalculationCategory()
                                {
                                    id = currentLevelTwoId,
                                    Calculationid = null,
                                    parentCalculationCategoryid = currentLevelOneId,
                                    label = label,
                                    amountid = vofL3.id,
                                    name = name,
                                    sortOrder = sortOrder,
                                    inclusionOption = GetInclusionOptionFromMengenart(l3.Mengenart),
                                    RefCalculationId = calculationId,
                                    description = l2.Positionstext,
                                    unit = GetUnitFromString(l2.Mengeneinheit),
                                    cost = l3.Preis,
                                    mappingid = l3.KagCode is null ? null : kagCodeToMappingId[l3.KagCode],
                                    Objektgliederung = l3.Objektgliederung,
                                    Positionslage = l3.Positionslage,
                                    color = l3.Menge != null ? BackgroundColor.Orange : null,
                                };

                                vofList.Add(vofL3);
                                calCatList.Add(calCatL3);
                                sortOrder++;
                            });
                        }
                    });
                });

                dbContext.AddRange(vofList);
                dbContext.AddRange(calCatList);
                dbContext.SaveChanges();
            }
            catch (Exception ex)
            {
                GeneralHelper.ThrowException($"Fehler beim Verarbeiten der Datei", ex.Message);
                throw;
            }
        }
        private static string GenerateL3Name (SiaRecordGGroup l2, SiaRecordGGroup.Unterposition l3 )
        {
            var og = l3.Objektgliederung;
            var pl = l3.Positionslage;

            string name = string.Empty;
            if (!string.IsNullOrEmpty(og) || !string.IsNullOrEmpty(pl))
            {
                name += "(";
                name += og ?? string.Empty;
                name += !string.IsNullOrEmpty(og) && !string.IsNullOrEmpty(pl) ? " - " : string.Empty;
                name += pl ?? string.Empty;
                name += ") ";
            }
            name += (l2.Überschrift ?? string.Empty).TrimEnd();
            return name;
        }
        private static InclusionOption GetInclusionOptionFromMengenart(string mengenart)
        {
            return mengenart switch
            {
                "A" => InclusionOption.INCLUDED,
                "B" => InclusionOption.INCLUDED,
                "C" => InclusionOption.INCLUDED,
                
                "J" => InclusionOption.BUDGETOPTION,
                "K" => InclusionOption.BUDGETOPTION,
                "L" => InclusionOption.BUDGETOPTION,
                
                "O" => InclusionOption.NOTINCLUDED, //möglicherweise ist es "0", nicht "O"
                "R" => InclusionOption.NOTINCLUDED,
                "S" => InclusionOption.NOTINCLUDED,

                "W" => InclusionOption.OPTION,

                _   => InclusionOption.INCLUDED
            };
        }
        private static ValueOrFormula CreateValueOrFormula(Guid calculationId, decimal? value)
        {
            return new ValueOrFormula { id = Guid.NewGuid(), RefCalculationId = calculationId, _value = value };
        }
        private static string getL1Name(List<SiaRecord> recs, string kapitelnummer)
        {
            var r = recs.OfType<SiaRecordG>().First(r => r.Kapitelnummer == kapitelnummer && r.Recordtyp == "1");
            return r.Positionstext + " " + r.SpezielleCodierungen;
        }

        private static Dictionary<string, Guid?> CreateKagCodeToMappingIdMap(TableContext dbContext, IEnumerable<SiaRecordB> recs, Guid calcId, int sparteId)
        {
            var kagCodeToBkp = recs.Where(r => !string.IsNullOrEmpty(r.KagCode))
                               .ToDictionary(r => r.KagCode, r => "BKP " + r.SpezielleCodierungen4);

            //var test = dbContext.Mapping.Where(m => m.label == "BKP 352.1");
            //var test2 = GeneralHelper.GetActualCalculation().ActualMappings;
            var bkpToMappingId = dbContext.Mapping
                .Where(m => kagCodeToBkp.Values.Contains(m.label.Trim()) && m.organizationUnit == sparteId && m.active == true)
                .GroupBy(b => b.label.Trim())
                .Select(g => g.First())
                .ToDictionary(m => m.label.Trim(), m => m.id);

            var kagToMappingId = new Dictionary<string, Guid?>();

            foreach (var (kagCode, bkp) in kagCodeToBkp)
            {
                if (bkpToMappingId.TryGetValue(bkp, out var mappingId))
                {
                    kagToMappingId[kagCode] = mappingId;
                }
                else
                {
                    kagToMappingId[kagCode] = null;
                }
            }

            return kagToMappingId;
        }

        private static Unit GetUnitFromString(string unitString)
        {
            if (string.IsNullOrEmpty(unitString))
            {
                return Unit.NONE;
            }
            
            return unitString.ToLower() switch
            {
                "m"  => Unit.M1,
                "m2" => Unit.M2,
                "m3" => Unit.M3,
                "%"  => Unit.PERCENT,
                "st" => Unit.PIECE,
                "St" => Unit.PIECE,
                "LE" => Unit.LE,
                "kg" => Unit.KG,
                "h"  => Unit.H,
                _    => Unit.NONE
            };
        }

    }
}
