package ru.bitel.bgbilling.modules.voice.dyn.mediator.asterisk;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;

import ru.bitel.bgbilling.apps.voice.accounting.mediation.AbstractMediator;
import ru.bitel.bgbilling.apps.voice.accounting.mediation.VoiceRecord;
import ru.bitel.bgbilling.apps.voice.accounting.mediation.VoiceRecordProcessor;
import ru.bitel.bgbilling.modules.voice.common.bean.VoiceDevice;
import ru.bitel.bgbilling.modules.voice.common.bean.VoiceDeviceType;
import ru.bitel.bgbilling.server.util.Setup;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.TimeUtils;
import ru.bitel.common.Utils;

public class AsteriskMediator_v2
    extends AbstractMediator
{
    protected String logPath;
    protected DateFormat ddmmyyyy_hhmmss = TimeUtils.getDateFormat( "dd.MM.yyyy HH:mm:ss" );
    
    @Override
    public Object init( Setup setup, int moduleId, VoiceDevice device, VoiceDeviceType deviceType, ParameterMap config )
        throws Exception
    {
        this.logPath = device.getLogPath();
        logger.debug( "logPath => " + logPath );
        return super.init( setup, moduleId, device, deviceType, config );
    }

    @Override
    public void readHourDataLog( VoiceRecordProcessor processor, Date hour )
        throws Exception
    {
        LocalDateTime time = TimeUtils.convertDateToLocalDateTime( hour );

        // предобработка логов
        Path rootPath = Paths.get( this.logPath );
        Path inboundPath = Paths.get( rootPath.toString(), "inbound" );

        if ( !Files.exists( inboundPath ) || !Files.isDirectory( inboundPath ) )
        {
            logger.error( "{} каталог не найден", inboundPath.toString() );
            return;
        }
        
        try
        {
            Files.list( inboundPath ).forEach( f -> process( rootPath, f ) );
        }
        catch( Exception ex )
        {
            logger.error( ex );
            return;
        }
        
        Path logPath = Paths.get( this.logPath );
        if ( Files.notExists( logPath ) )
        {
            Files.createDirectories( logPath );
        }
        
        // день + час за который надо обработать логи
        String prefix = time.format( DateTimeFormatter.ofPattern( "yyyyMMddHH" ) );
        
        Path hourLogPath = Paths.get( logPath.toString(), prefix.substring( 0, 4 ), prefix.substring( 4, 6 ), prefix.substring( 6, 8 ) + "_" + prefix.substring( 8 ) + ".zip" );
        logger.debug( "hourLogPath => " + hourLogPath.toString() );
        if ( Files.exists( hourLogPath ) )
        {
            ZipInputStream zipInputStream = new ZipInputStream( Files.newInputStream( hourLogPath ) );
            zipInputStream.getNextEntry();
            String cdrs = new String( zipInputStream.readAllBytes() );
            zipInputStream.close();
            
            Arrays.asList( cdrs.split( "\n" ) ).forEach( line ->
            {
                try 
                {
                    processLine( processor, line.split( "\t" ) );    
                }
                catch( Exception e )
                {
                    logger.error( e );
                }
            } );
        }
    }

    protected VoiceRecord processLine( final VoiceRecordProcessor processor, final String[] params )
        throws InterruptedException
    {
        //- 0 дата и время начала звонка (dd.MM.yyyy HH:mm:ss)
        //- 1 длительность звонка (секунды)
        //- 2 # A
        //- 3 # A (E.164), далее #A164
        //- 4 # B
        //- 5 # B (E.164), далее #B164
        //- 6 port_from
        //- 7 port_to
        //- 8 категория звонка
        //- 9 время соединения (секунды)
        //- 10 стоимость вызова
        //- 11 стоимость вызова для оператора
        //
        // 01.08.2006 10:55:18 57733 3517913292 73517913292 2479292 73512479292 3 62 5 57733 0
        
        logger.debug( "processLine => " + params[0] + " " + params[1] + " " + params[3] + " " + params[5] );
        
        final VoiceRecord record = processor.next();
        record.sessionStart = TimeUtils.parseDate( params[0], ddmmyyyy_hhmmss );
        record.duration = record.connectionDuration = Utils.parseInt( params[1], 0 );
        record.callingStationId = params[2];
        record.e164CallingStationId = params[3];
        record.calledStationId = params[4];
        record.e164CalledStationId = params[5];
        record.trunkIncoming = params[6];
        record.trunkOutgoing = params[7];
        record.category = Utils.parseInt( params[8] );
        
        return record;
    }
    
    protected String toE164( String str )
    {
        if ( str.startsWith( "810" ) )
        {
            str = str.replaceAll( "^810(\\d+)$", "$1" );
        }
        else if ( str.startsWith( "8" ) )
        {
            str = str.replaceAll( "^8(\\d{10})$", "7$1" );
        }
        else
        {
            str = str.replaceAll( "^(\\d{10})$", "7$1" );
        }
        return str;
    }
    
    /**
     * разбор csv строки
     * @param line
     * @return
     */
    private static List<String> parseLine( String line )
    {
        List<String> data = new ArrayList<>();
        if ( line == null || line.isBlank() )
        {
            return data;
        }
        int index = 0;
        String value = "";
        while ( index < line.length() )
        {
            char ch = line.charAt( index++ );
            // разбор строкового поля
            if ( ch == '"' )
            {
                while ( index < line.length() )
                {
                    ch = line.charAt( index++ );
                    if ( ch != '"' )
                    {
                        value += ch;
                    }
                    else
                    {
                        if ( index < line.length() )
                        {
                            ch = line.charAt( index++ );
                            if ( ch == ',' )
                            {
                                break;
                            }
                            else
                            {
                                value += ch;
                            }
                        }
                        else
                        {
                            break;
                        }
                    }
                }                
            }
            // разбор числового поля
            else
            {
                while ( index < line.length() )
                {
                    ch = line.charAt( index++ );
                    if ( ch != ',' )
                    {
                        value += ch;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            data.add( value );
            //logger.info( data.size() + " " + value );
            value = "";
        }
        return data;
    }
    
    // файл лога с названием типа yyyy-mm-dd.csv
    private void process( Path rootPath, Path path )
    {
        logger.info( "Process file {}", path.toString() );
        if ( !Files.isRegularFile( path ) )
        {
            logger.error( "Skip {}", path.toString() );
            return;
        }
        
        //- дата и время начала звонка (dd.MM.yyyy HH:mm:ss)
        //- длительность звонка (секунды)
        //- # A
        //- # A (E.164), далее #A164
        //- # B
        //- # B (E.164), далее #B164
        //- port_from
        //- port_to
        //- категория звонка
        //- время соединения (секунды)
        //- стоимость вызова
        //- стоимость вызова для оператора
        //
        // 01.08.2006 10:55:18 57733 3517913292 73517913292 2479292 73512479292 3 62 5 57733 0
        //
        // 2005 |
        //      |-01 |
        //           |-01_00.zip
        //           |-01_01.zip
        try
        {
            DateFormat inDateFormat = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); 
            DateFormat outDateFormat = new SimpleDateFormat( "dd.MM.yyyy HH:mm:ss" );
            Map<String,List<String>> cdrs = new HashMap<>();
            Files.lines( path ).forEach( line ->
            {
                List<String> strings = parseLine( line );
                if ( !( strings.get( 3 ).endsWith( "-out" ) &&
                      "ANSWERED".equals( strings.get( 13 ) ) &&
                      strings.get( 17 ).length() > 6 ) )
                {
                    //logger.info( "skip: " + line );
                    return;
                }
                //
                String start = null;
                try
                {
                    start = outDateFormat.format( inDateFormat.parse( strings.get( 8 ) ) );
                }
                catch (Exception ex) 
                {
                    logger.error( ex );
                    return;
                }
                //
                String cdr = start + "\t";
                cdr += strings.get( 11 ) + "\t";
                cdr += strings.get( 17 ) + "\t";
                cdr += toE164( strings.get( 17 ) ) + "\t";
                cdr += strings.get( 2 ) + "\t";
                cdr += toE164( strings.get( 2 ) ) + "\t";
                cdr += strings.get( 4 ) + "\t";
                cdr += strings.get( 5 ) + "\t";
                cdr += "0\t";
                cdr += strings.get( 11 ) + "\t";
                cdr += "0\t";
                cdr += "0";
                
                String key = cdr.substring( 0, 13 );
                List<String> list = cdrs.get( key ) ;
                if ( list == null )
                {
                    list = new ArrayList<>();
                    cdrs.put( key, list );
                }
                list.add( cdr );
            } );
            // раскладываем по часовым файлам
            cdrs.forEach( ( key, list ) ->
            {
                Path filePath = Paths.get( rootPath.toString(), key.substring( 6, 10 ), key.substring( 3, 5 ), key.substring( 0, 2 ) + "_" + key.substring( 11, 13 ) + ".zip" );
                logger.info( filePath.toString() );
                // создаем промежуточные каталоги
                try
                {
                    Files.createDirectories( filePath.getParent() );
                }
                catch( IOException ex )
                {
                    logger.error( ex ) ;
                    return;
                }
                StringBuffer fileData = new StringBuffer();
                try
                {
                    // если файл существует считываем из него данные
                    if ( Files.exists( filePath ) )
                    {
                        ZipInputStream zipInputStream = new ZipInputStream( Files.newInputStream( filePath ) );
                        zipInputStream.getNextEntry();
                        fileData.append( new String( zipInputStream.readAllBytes() ) );
                        zipInputStream.close();
                        //
                        Files.delete( filePath );
                    }
                }
                catch ( Exception ex ) 
                {
                    logger.error( ex );
                    return;
                }
                // дописываем новые
                List<String> listFromFile = Arrays.asList( fileData.toString().split( "\n" ) );
                list.forEach( a ->
                {
                    if ( !listFromFile.contains( a ) )
                    {
                        fileData.append( a ).append( "\n" );
                    }
                } );
                // сохраняем
                try
                {
                    ZipOutputStream zipOutputStream = new ZipOutputStream( Files.newOutputStream( filePath ) );
                    ZipEntry zipEntry = new ZipEntry( key.substring( 0, 2 ) + "_" + key.substring( 11, 13 ) );
                    zipOutputStream.putNextEntry( zipEntry );
                    zipOutputStream.write( fileData.toString().getBytes() );
                    zipOutputStream.close();
                }
                catch ( Exception ex ) 
                {
                    logger.error( ex );
                    return;
                }
            } );
        }
        catch( Exception ex )
        {
            logger.error( ex );
        }
        
        try
        {
            Path processedPath = Paths.get( rootPath.toString(), "processed" );
            Files.createDirectories( processedPath );
            String[] yyyymmdd = path.getFileName().toString().split( "-" );
            Path dstPath = Paths.get( processedPath.toString(), yyyymmdd[0], yyyymmdd[1], path.getFileName().toString() );
            Files.createDirectories( dstPath.getParent() );
            Files.move( path, dstPath );
        }
        catch (Exception ex) 
        {
            logger.error( ex );
        }
    }    
    
    public static void main( String[] args )
    {
        // настройка логера
        ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();

        AppenderComponentBuilder console = builder.newAppender( "stdout", "Console" );
        builder.add( console );

        RootLoggerComponentBuilder rootLogger = builder.newRootLogger( Level.INFO );
        rootLogger.add( builder.newAppenderRef( "stdout" ) );
        builder.add( rootLogger );
        
        Configurator.initialize( builder.build() );

        Path rootPath = Paths.get( args[0] );
        Path inboundPath = Paths.get( rootPath.toString(), "inbound" );

        if ( !Files.exists( inboundPath ) || !Files.isDirectory( inboundPath ) )
        {
            System.out.println( inboundPath.toString() + " каталог не найден" );
            System.exit( 1 );
        }
        
        try
        {
//            Files.list( inboundPath ).forEach( f -> process( rootPath, f ) );
        }
        catch( Exception ex )
        {
            System.err.println( ex );
            System.exit( 2 );
        }
    }    
}
