package com.utopiaindustries.qualitychecker.service;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;

import androidx.annotation.NonNull;

import com.utopiaindustries.qualitychecker.apiservice.ApiService;
import com.utopiaindustries.qualitychecker.apiservice.ApiServiceFactory;
import com.utopiaindustries.qualitychecker.db.CheckpointRepository;
import com.utopiaindustries.qualitychecker.db.DefectRepository;
import com.utopiaindustries.qualitychecker.db.DimensionRepository;
import com.utopiaindustries.qualitychecker.db.InspectionCheckpointSkuRepository;
import com.utopiaindustries.qualitychecker.db.InspectionLabelRepository;
import com.utopiaindustries.qualitychecker.db.ItemRepository;
import com.utopiaindustries.qualitychecker.db.ProductRepository;
import com.utopiaindustries.qualitychecker.db.ReportRepository;
import com.utopiaindustries.qualitychecker.models.InspectionCheckPoint;
import com.utopiaindustries.qualitychecker.models.InspectionCheckpointSku;
import com.utopiaindustries.qualitychecker.models.InspectionDefect;
import com.utopiaindustries.qualitychecker.models.InspectionDimension;
import com.utopiaindustries.qualitychecker.models.InspectionItemCheckPoint;
import com.utopiaindustries.qualitychecker.models.InspectionItemDefect;
import com.utopiaindustries.qualitychecker.models.InspectionLabelResponse;
import com.utopiaindustries.qualitychecker.models.InspectionReport;
import com.utopiaindustries.qualitychecker.models.InspectionReportWrapper;
import com.utopiaindustries.qualitychecker.models.ItemUnit;
import com.utopiaindustries.qualitychecker.models.Product;
import com.utopiaindustries.qualitychecker.models.callback.SaveCheckpointCallback;
import com.utopiaindustries.qualitychecker.models.callback.SaveDefectCallback;
import com.utopiaindustries.qualitychecker.models.callback.SaveDimensionCallback;
import com.utopiaindustries.qualitychecker.models.callback.SaveDraftReportCallback;
import com.utopiaindustries.qualitychecker.models.callback.SaveInspectionLabelCallBack;
import com.utopiaindustries.qualitychecker.models.callback.SaveItemCallback;
import com.utopiaindustries.qualitychecker.models.callback.SaveProductCallBack;
import com.utopiaindustries.qualitychecker.models.callback.SaveReportCallback;
import com.utopiaindustries.qualitychecker.store.Store;
import com.utopiaindustries.qualitychecker.utils.FileUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class InspectionReportService {

    private static InspectionReportService instance;
    private ApiService apiService;
    private List<String> defectTypes;
    private List<String> dimensionTypes;
    private List<String> reportResult;
    private Store store;

    // singleton instance
    public synchronized static  InspectionReportService getInstance(){
        if( instance == null ){
            instance = new InspectionReportService();
        }
       return instance;
    }

    //  initialize api service
    {
        apiService = ApiServiceFactory.getApiService();
        defectTypes  = Arrays.asList( "CRITICAL","MINOR","MAJOR" );
        dimensionTypes = Arrays.asList( "LENGTH", "WIDTH", "DROP" );
        reportResult = Arrays.asList("PASSED","FAILED","ON_HOLD");
        store = Store.getInstance();
    }

     public void isUserAuthenticated( String username,
                                      String password,
                                      String[] roles,
                                      Callback<Boolean> callback){


         apiService.isUserAuthenticated(username, password, roles).enqueue(
                 new Callback<Boolean>() {
                     @Override
                     public void onResponse(@NonNull Call<Boolean> call, @NonNull Response<Boolean> response) {
                         if (response.isSuccessful()) {
                             callback.onResponse(call, response);
                         } else {
                             // Handle error
                             callback.onFailure(call, new Throwable("Failed to authenticate user"));
                         }
                     }
                     @Override
                     public void onFailure(@NonNull Call<Boolean> call, @NonNull Throwable t) {
                         // Handle failure
                         callback.onFailure(call, t);
                     }
         });
     }

     public List<String> getDefectTypes(){
        return defectTypes;
     }
     public List<String> getDimensionTypes(){return dimensionTypes;}

    public List<String> getReportResult() {
        return reportResult;
    }

    public void setReportResult(List<String> reportResult) {
        this.reportResult = reportResult;
    }

    private synchronized void populateDefectsInBackground(InspectionReport inspectionReport) {
        for (InspectionItemCheckPoint itemCp : inspectionReport.getItems().get(0).getCheckPoints()) {
            List<InspectionItemDefect> defectList = getInspectionItemDefects(itemCp);
            itemCp.setDefects(defectList);
        }
    }


    private void saveReportInBackground(final InspectionReport inspectionReport, final SaveReportCallback callback) {
        // Post object
        apiService.saveInspectionReport(inspectionReport).enqueue(
                new Callback<Map<String, String>>() {
                    @Override
                    public void onResponse(Call<Map<String, String>> call,
                                           Response<Map<String, String>> response) {
                        if (response.isSuccessful()) {
                            System.out.println(response.body());
                            if (response.body().get("result").equalsIgnoreCase("success")) {
                                callback.onSuccess();
                            } else {
                                callback.onFailure(new Exception("Error on submitting report"));
                            }
                        } else {
                            callback.onFailure(new Exception("API call failed with status code: " + response.code()));
                        }
                    }

                    @Override
                    public void onFailure(Call<Map<String, String>> call, Throwable t) {
                        System.out.println(t);
                        callback.onFailure(t);
                    }
                }
        );
    }

    private synchronized void saveReportInDb( final Context context,
                                 final InspectionReport inspectionReport,
                                 final SaveReportCallback callback) throws IOException {
        try {
            // convert into byte array
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream( byteArrayOutputStream );
            objectOutputStream.writeObject( inspectionReport );
            objectOutputStream.flush();
            byte[] bytes =  byteArrayOutputStream.toByteArray();
            // write bytes in file
            long epochSeconds = System.currentTimeMillis() / 1000;
            String fileName = "Report-" + epochSeconds + ".bin";
            String savedPath = FileUtils.writeFile( context, bytes,  fileName );
            System.out.println( savedPath );
            new ReportRepository( context ).insert(
                    new InspectionReportWrapper( 0, savedPath, false )
            );
            callback.onSuccess();
        } catch (  Exception  e ){
            callback.onFailure( new Exception( "Failed to Save in DB" ) );
             e.printStackTrace();
        }
    }

    private synchronized void saveDraftReportInDb( final Context context,
                                              final InspectionReport inspectionReport,
                                              final SaveDraftReportCallback callback) throws IOException {

        SharedPreferences sharedPreferences = context.getSharedPreferences("login_prefs", Context.MODE_PRIVATE);
        long draftReportId = Long.parseLong(sharedPreferences.getString("draftReportId", null));
        //System.out.println(sharedPreferences.getString("draftReportId", null));
        //System.out.println("Draft Button Click-8: " + draftReportId);

        try {
            // convert into byte array
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream( byteArrayOutputStream );
            objectOutputStream.writeObject( inspectionReport );
            objectOutputStream.flush();
            byte[] bytes =  byteArrayOutputStream.toByteArray();
            // write bytes in file
            long epochSeconds = System.currentTimeMillis() / 1000;
            String fileName = "Report-" + epochSeconds + ".bin";
            String savedPath = FileUtils.writeFile( context, bytes,  fileName );
            //System.out.println(fileName + "---" + savedPath );
            new ReportRepository( context ).insertDraft(
                    new InspectionReportWrapper( draftReportId, savedPath, false ),
                    store.getReport().getItems().get(0).getFnsku()
            );
            callback.onSuccess();
        } catch (  Exception  e ){
            callback.onFailure( new Exception( "Failed to Save in DB" ) );
            e.printStackTrace();
        }
    }

    public void saveDraftInLocalDb( Context context,
                               InspectionReport inspectionReport ,
                               SaveDraftReportCallback callback ){

            new Thread(() -> {
                populateDefectsInBackground( inspectionReport );
                //post report
                try {
                    saveDraftReportInDb( context, inspectionReport , callback );
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }).start();
    }

    public void saveInLocalDb( Context context,
                               InspectionReport inspectionReport ,
                               SaveReportCallback callback ){
        if( Objects.nonNull( inspectionReport ) && inspectionReport.getItems().get(0).getSku() != null && inspectionReport.getItems() != null &&
                ! inspectionReport.getItems().isEmpty() &&
                ! inspectionReport.getItems().get(0).getCheckPoints().isEmpty() ){
            new Thread(() -> {
                populateDefectsInBackground( inspectionReport );
                //post report
                try {
                    saveReportInDb( context, inspectionReport , callback );
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }).start();
        }
    }

    public void saveReport(InspectionReport inspectionReport , SaveReportCallback callback){

        if( Objects.nonNull( inspectionReport ) && inspectionReport.getItems().get(0).getSku() != null && inspectionReport.getItems() != null &&
                ! inspectionReport.getItems().isEmpty() &&
                ! inspectionReport.getItems().get(0).getCheckPoints().isEmpty() ){

            new Thread(() -> {
                populateDefectsInBackground( inspectionReport );
                //post report
                saveReportInBackground( inspectionReport , callback );
            }).start();
        } else {
            callback.onFailure( new Exception("Please Fill the required Fields..") );
        }
     }

    @NonNull
    private static List<InspectionItemDefect> getInspectionItemDefects(InspectionItemCheckPoint itemCp) {
        List<InspectionItemDefect> defectList = new ArrayList<>();
        for (int i = 0; i < itemCp.getDefectTitles().size() ; i++ ){
            InspectionItemDefect defect = new InspectionItemDefect();
            defect.setDefectSeverity(itemCp.getDefectSeverites().get( i ) );
            defect.setTitle( itemCp.getDefectTitles().get( i ) );
            defect.setQuantity( itemCp.getQuantities().get( i ) );
            defectList.add( defect );
        }
        return defectList;
    }


    public void fetchAndPopulateProducts( SaveProductCallBack callback,
                                          ProductRepository repository){
        new Thread(() -> {
            populateProducts( callback, repository );
        }).start();
    }

    public void fetchAndPopulateInspectionLabel( SaveInspectionLabelCallBack callback,
                                          InspectionLabelRepository repository){
        new Thread(() -> {
            populateInspectionLabels( callback, repository );
        }).start();
    }

    public void fetchAndPopulateDimensions( SaveDimensionCallback callback,
                                            DimensionRepository repository ){
            new Thread(() -> {
                populateDimensions( callback, repository );
            }).start();
    }

    public void fetchAndPopulateCheckpoints( SaveCheckpointCallback callback ,
                                             CheckpointRepository repository ){
        new Thread(() -> {
            populateCheckpoints( callback, repository );
        }).start();
    }

    public void fetchAndPopulateDefects( SaveDefectCallback callback,
                                         DefectRepository repository ){
        new Thread( () -> {
            populateDefects( callback, repository );
        }).start();
    }

    public void fetchAndPopulateUnits( SaveItemCallback callback,
                                      ItemRepository repository ){
        new Thread(() -> {
            populateUnits( callback, repository );
        }).start();
    }

    public void fetchAndPopulateSkuCheckpoints( SaveItemCallback callback,
                                                InspectionCheckpointSkuRepository repository ){
        new Thread(() -> {
            populateSkuCheckpoints( callback, repository );
        }).start();
    }


    private void populateProducts( SaveProductCallBack callback,
                                   ProductRepository repository ){
        apiService.fetchAllProducts(true).enqueue(
                new Callback<List<Product>>() {
                    @Override
                    public void onResponse(Call<List<Product>> call,
                                           Response<List<Product>> response) {
                        if (response.isSuccessful()) {
                            try {
                                System.out.println( response.body() );
                                callback.onSuccess();
                                repository.insert( response.body() );
                            } catch ( Exception ex ){
                                callback.onFailure(new Exception( ex.getMessage() ) );
                            }
                        } else {
                            callback.onFailure(new Exception("API call failed with status code: " + response.code()));
                        }
                    }
                    @Override
                    public void onFailure(Call<List<Product>> call, Throwable t) {
                        System.out.println(t);
                        callback.onFailure(t);
                    }
                }
        );
    }

    private void populateInspectionLabels( SaveInspectionLabelCallBack callback,
                                   InspectionLabelRepository repository ){
        apiService.fetchAllInspectionLabels().enqueue(
                new Callback<InspectionLabelResponse>() {
                    @Override
                    public void onResponse(Call<InspectionLabelResponse> call,
                                           Response<InspectionLabelResponse> response) {
                        if (response.isSuccessful()) {
                            try {
                                System.out.println( response.body() );
                                callback.onSuccess();
                                assert response.body() != null;
                                repository.insertInspectionLabels( response.body().getInspectionLabel(), response.body().getQualityLabel() );
                            } catch ( Exception ex ){
                                callback.onFailure(new Exception( ex.getMessage() ) );
                            }
                        } else {
                            callback.onFailure(new Exception("API call failed with status code: " + response.code()));
                        }
                    }
                    @Override
                    public void onFailure(Call<InspectionLabelResponse> call, Throwable t) {
                        System.out.println(t);
                        callback.onFailure(t);
                    }
                }
        );
    }

    private void populateDimensions( SaveDimensionCallback callback,
                                     DimensionRepository repository ){

        apiService.fetchDimensions( ).enqueue(new Callback<List<InspectionDimension>>() {
            @Override
            public void onResponse(Call<List<InspectionDimension>> call,
                                   Response<List<InspectionDimension>> response) {
                if (response.isSuccessful()) {
                    try {
                        System.out.println( response.body() );
                        callback.onSuccess();
                        repository.insert( response.body() );
                    } catch ( Exception ex ){
                        callback.onFailure(new Exception( ex.getMessage() ) );
                    }
                } else {
                    callback.onFailure(new Exception("API call failed with status code: " + response.code()));
                }

            }
            @Override
            public void onFailure(Call<List<InspectionDimension>> call, Throwable t) {
                System.out.println( t );
                callback.onFailure( t );
            }
        });
    }


    private void populateCheckpoints( SaveCheckpointCallback callback,
                                      CheckpointRepository repository ){

        apiService.fetchCheckPoints().enqueue(
                new Callback<List<InspectionCheckPoint>>() {
                    @Override
                    public void onResponse( Call<List<InspectionCheckPoint>> call,
                                            Response<List<InspectionCheckPoint>> response ) {
                        if (response.isSuccessful()) {
                            try {
                                System.out.println( response.body() );
                                callback.onSuccess();
                                repository.insert( response.body() );
                            } catch ( Exception ex ){
                                callback.onFailure(new Exception( ex.getMessage() ) );
                            }
                        } else {
                            callback.onFailure(new Exception("API call failed with status code: " + response.code()));
                        }
                    }
                    @Override
                    public void onFailure( Call<List<InspectionCheckPoint>> call,
                                           Throwable t) {
                        System.out.println( t );
                        callback.onFailure( t );
                    }
                }
        );
    }

    private void populateDefects( SaveDefectCallback callback,
                                  DefectRepository repository ){
        apiService.fetchDefects().enqueue(new Callback<List<InspectionDefect>>() {
            @Override
            public void onResponse(Call<List<InspectionDefect>> call, Response<List<InspectionDefect>> response) {
                if (response.isSuccessful()) {
                    try {
                        System.out.println( response.body() );
                        callback.onSuccess();
                        repository.insert( response.body() );
                    } catch ( Exception ex ){
                        callback.onFailure(new Exception( ex.getMessage() ) );
                    }
                } else {
                    callback.onFailure(new Exception("API call failed with status code: " + response.code()));
                }
            }
            @Override
            public void onFailure(Call<List<InspectionDefect>> call, Throwable t) {
                System.out.println( t );
                callback.onFailure( t );
            }
        });
    }

    private void populateUnits( SaveItemCallback callback ,
                               ItemRepository repository ){
        apiService.fetchItemUnits().enqueue(
                new Callback<List<ItemUnit>>() {
                    @Override
                    public void onResponse( Call<List<ItemUnit>> call,
                                            Response<List<ItemUnit>> response) {
                        if (response.isSuccessful()) {
                            try {
                                System.out.println( response.body() );
                                callback.onSuccess();
                                repository.insert( response.body() );
                            } catch ( Exception ex ){
                                callback.onFailure(new Exception( ex.getMessage() ) );
                            }
                        } else {
                            callback.onFailure(new Exception("API call failed with status code: " + response.code()));
                        }
                    }
                    @Override
                    public void onFailure( Call<List<ItemUnit>> call,
                                           Throwable t) {
                        System.out.println( t );
                        callback.onFailure( t );
                    }
                }
        );
    }

    /*
    *  populate sku checkpoints tagging
    * */
    private void populateSkuCheckpoints( SaveItemCallback callback ,
                                        InspectionCheckpointSkuRepository repository){
        apiService.fetchSkuCheckpoints().enqueue(
                new Callback<List<InspectionCheckpointSku>>() {
                    @Override
                    public void onResponse( Call<List<InspectionCheckpointSku>> call,
                                            Response<List<InspectionCheckpointSku>> response) {
                        if (response.isSuccessful()) {
                            try {
                                System.out.println( response.body() );
                                callback.onSuccess();
                                repository.insert( response.body() );
                            } catch ( Exception ex ){
                                callback.onFailure(new Exception( ex.getMessage() ) );
                            }
                        } else {
                            callback.onFailure(new Exception("API call failed with status code: " + response.code()));
                        }
                    }
                    @Override
                    public void onFailure( Call<List<InspectionCheckpointSku>> call,
                                           Throwable t) {
                        System.out.println( t );
                        callback.onFailure( t );
                    }
                }
        );
    }
}