package com.aip.gateway.api.aip.manager;

import com.aip.gateway.api.aip.AuthDelegateImpl;
import com.aip.gateway.api.aip.ConsentDelegateImpl;
import com.aip.gateway.api.aip.model.*;
import com.aip.gateway.api.aip.utils.Utilities;
import com.aip.gateway.api.aip.utils.AipFileUtils;
import com.microsoft.informationprotection.*;
import com.microsoft.informationprotection.file.*;
import com.microsoft.informationprotection.internal.callback.FileHandlerObserver;
import com.microsoft.informationprotection.internal.file.PFileExtensionBehavior;
import com.microsoft.informationprotection.protection.IProtectionHandler;
import com.microsoft.informationprotection.protection.Rights;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

@Slf4j
public class FileManager extends AbstractManager {
    private IFileProfile _profile = null;
    private IFileEngine _engine = null;


    @Override
    public void dispose() {
        dispose(true);
    }
    void dispose(boolean disposing) {
        synchronized(this) {
            if (_profile != null & _engine != null) {
                //_profile.UnloadEngineAsync(_engine.Settings.EngineId).Wait();
            }
            _engine = null;
            _profile = null;
        }
    }

    @Override
    public boolean createProfile(MipContext mipContext) {
        try {
            ConsentDelegateImpl consentDelegate = new ConsentDelegateImpl();

            FileProfileSettings profileSettings = new FileProfileSettings(mipContext,
                    com.microsoft.informationprotection.CacheStorageType.ON_DISK_ENCRYPTED,
                    consentDelegate);
            Future<IFileProfile> profileFuture = MIP.loadFileProfileAsync(profileSettings);
            _profile = profileFuture.get();
        }
        catch(InterruptedException ie) {
            setError(1, "FileManager::createProfile InterruptedException Exception.", ie.getMessage());
            Thread.currentThread().interrupt();
            return false;
        }
        catch(ExecutionException eex) {
            setError(2, "FileManager::createProfile ExecutionException Exception.", eex.getMessage());
            return false;
        }
        catch (Exception e) {
            setError(3, "FileManager::createProfile Exception.", e.getMessage());
            return false;
        }
        return _profile != null;
    }

    @Override
    public boolean createEngine(Identity identity, AuthDelegateImpl authDelegate) {
        try {
            authDelegate.resetError();
            // 보호 엔진 설정 개체를 만듭니다.
            // 첫 번째 매개변수인 엔진 ID에 빈 문자열을 전달하면 SDK 가 GUID 를 생성 합니다.
            // 이메일 주소나 기타 고유한 값을 전달하면 동일한 사용자에 대해 캐시된 엔진이 매번 로드 되도록 하는 데 도움이 됩니다.
            // 로캘 설정은 지원되며 특히 클라이언트 응용 프로그램의 경우 컴퓨터 로캘을 기반 으로 제공 되어야 합니다.
            FileEngineSettings engineSettings = new FileEngineSettings(identity.getEmail(), authDelegate, "", "en-US");

            // Uncomment to set a functionality filter. These filters are useful for filtering or including labels based on protection type.
            // The example below will result in removal of labels with user-defined permissions.
            //engineSettings.configureFunctionality(FunctionalityFilterType.CUSTOM, false);

            // Set the user identity for the engine. This aids in service discovery.
            engineSettings.setIdentity(identity);

            // Add the engine and get the result.
            Future<IFileEngine> fileEngineFuture = _profile.addEngineAsync(engineSettings);
            _engine = fileEngineFuture.get();
        }
        catch(InterruptedException ie) {
            setError(1, "FileManager::createEngine InterruptedException Exception.", ie.getMessage());
            Thread.currentThread().interrupt();
            return false;
        }
        catch(ExecutionException eex) {
            setError(2, "FileManager::createEngine ExecutionException Exception.", eex.getMessage());
            return false;
        }
        catch (Exception e) {
            setError(3, "FileManager::createEngine Exception.", e.getMessage());
            return false;
        }
        return _engine != null;
    }
    public List<Label> sensitivityLabels() {
        Collection<Label> labels = _engine.getSensitivityLabels();
//        labels.forEach(label -> {
//            log.info(label.getName() + " : " + label.getId());
//            if (!label.getChildren().isEmpty()) {
//                label.getChildren().forEach(child -> {
//                    log.info("\t" + child.getName() + " : " + child.getId());
//                });
//            }
//        });
        return new ArrayList<>(labels);
    }

    public long getFileSize(String fileName) {
        try {
            File file = new File(fileName);
            return file.length();
        }
        catch(Exception ex) {
            return 0;
        }
    }

    public AipFileInfo getFileInfo(IStream fileStream, String outputFileName) {
        IFileHandler handler = createFileHandler(fileStream, outputFileName);
        if (handler == null) {
            //AipFileUtils.delete(outputFileName);
            return null;
        }
        AipFileInfo result = getFileInfo(handler);
        try {
            result.setFileSize(fileStream.size());
            //AipFileUtils.delete(outputFileName);
        }
        catch (Exception ex) {
            result.setFileSize(0);
            lastErrMsg = ex.getMessage();
        }
        return result;
    }
    public AipFileInfo getFileInfo(String fileName) {
        IFileHandler handler = createFileHandler(fileName, fileName);
        if (handler == null) {
            return null;
        }
        AipFileInfo result = getFileInfo(handler);
        if (result != null) {
            result.setFileSize(getFileSize(fileName));
        }
        return result;
    }

    private AipFileInfo getFileInfo(IFileHandler handler) {
        if (handler == null) {
            return null;
        }

        AipFileInfo fileInfo = new AipFileInfo(0, "");

        log.error("AipFileInfo: {}", handler.getOutputFileName());

        fileInfo.setOutputFileName(handler.getOutputFileName());

        if (handler.getLabel() != null) {
            AipContentLabel content = AipContentLabel.builder()
                    .creationTime(handler.getLabel().creationTime)
                    .assignmentMethod(handler.getLabel().assignmentMethod.ordinal())
                    .isProtectionAppliedFromLabel(handler.getLabel().isProtectionAppliedFromLabel)
                    .build();
            fileInfo.setContent(content);
            fileInfo.setLabel(Utilities.labelToAip(handler.getLabel().label));
        }

        if (handler.getProtection() != null) {
            IProtectionHandler protection = handler.getProtection();
            AipProtection aipProtection = AipProtection.builder()
                    .owner(protection.getOwner())
                    .issuedTo(protection.getIssuedTo())
                    .isIssuedToOwner(protection.getIsIssuedToOwner())
                    .contentId(protection.getContentId())
                    .auditedExtractAllowed(protection.getAuditedExtractAllowed())
                    .blockSize(protection.getBlockSize())
                    .protectionDescriptor(null)
                    .useDeprecatedAlgorithms(protection.getUseDeprecatedAlgorithms())
                    .build();

            fileInfo.setProtection(aipProtection);

            if (handler.getProtection().getProtectionDescriptor() != null) {
                ProtectionDescriptor data = handler.getProtection().getProtectionDescriptor();
                AipProtectionDescriptor descriptor = AipProtectionDescriptor.builder()
                        .protectionType(data.getProtectionType().ordinal())
                        .templateId(data.getTemplateId())
                        .labelInformation(new AipLabelInfo("x", "x"))
                        .labelId(data.getLabelId())
                        .owner(data.getOwner())
                        .contentId(data.getContentId())
                        .name(data.getName())
                        .description(data.getDescription())
                        .allowOfflineAccess(data.isAllowOfflineAccess())
                        .referrer(data.getReferrer())
                        .contentValidUntil(data.getContentValidUntil())
                        .doubleKeyUrl(data.getDoubleKeyUrl())
                        .build();

                if (data.getLabelInfo() != null) {
                    descriptor.getLabelInformation().setLabelId(data.getLabelInfo().getLabelId());
                    descriptor.getLabelInformation().setTenantId(data.getLabelInfo().getTenantId());
                }
                fileInfo.getProtection().setProtectionDescriptor(descriptor);
            }
        }
        return fileInfo;
    }

    public Label getLabelById(String labelId) {
        Label label;
        try {
            label = _engine.getLabelById(labelId);
        }
        catch (Exception ex) {
            setError(99, "FileManager::GetLabel Failed. Request Label Id: " + labelId, ex.getMessage());
            return null;
        }
        return label;
    }
    private IFileHandler createFileHandler(String inputFile, String outputFile) {
        try {
            if (!AipFileUtils.isExists(inputFile)) {
                setError(91, "FileManager::createFileHandler Failed.", "요청한 파일이 존재하지 않습니다. " + inputFile);
                return null;
            }
            FileHandlerObserver observer = new FileHandlerObserver();
            Future<IFileHandler> handlerFuture = _engine.createFileHandlerAsync(inputFile, outputFile, true, observer, null);
            return handlerFuture.get();
        } catch (InterruptedException e) {
            setError(93, "FileManager::CreateFileHandler InterruptedException Exception.", e.getMessage());
            Thread.currentThread().interrupt();
        } catch (ExecutionException e) {
            setError(94, "FileManager::CreateFileHandler ExecutionException Exception.", e.getMessage());
        }
        return null;
    }
    private IFileHandler createFileHandler(IStream inputStream, String outputFile) {
        if (inputStream == null) {
            setError(91, "FileManager::CreateFileHandler Failed.", "요청한 스트림의 정보가 없습니다.");
            return null;
        }
        if (outputFile == null || outputFile.isEmpty()) {
            setError(92, "FileManager::CreateFileHandler Failed.", "요청한 출력 파일 이릅이 존재하지 않습니다.");
            return null;
        }

        try {
            FileHandlerObserver observer = new FileHandlerObserver();
            Future<IFileHandler> handlerFuture = _engine.createFileHandlerAsync(inputStream, outputFile, true, observer, null);
            return handlerFuture.get();
        } catch (InterruptedException e) {
            setError(93, "FileManager::CreateFileHandler InterruptedException Exception.", e.getMessage());
            Thread.currentThread().interrupt();
        } catch (ExecutionException e) {
            setError(94, "FileManager::CreateFileHandler ExecutionException Exception.", e.getMessage());
        }

        return null;
    }
    public SetFileInfo setLabel(String fileName, String actualFileName, String email, String labelId, String templateId, String comments) {
        IFileHandler handler = createFileHandler(fileName, actualFileName);
        return setLabel(handler, actualFileName, email, labelId, templateId, comments, getFileSize(fileName));
    }
    public SetFileInfo setLabel(IStream fileStream, String actualFileName, String email, String labelId, String templateId, String comments) {
        IFileHandler handler = createFileHandler(fileStream, actualFileName);
        return setLabel(handler, actualFileName, email, labelId, templateId, comments, fileStream.size());
    }
    private SetFileInfo setLabel(IFileHandler handler, String actualFileName, String email, String labelId, String templateId, String comments, long fileSize) {
        SetFileInfo result = new SetFileInfo();
        if (handler == null) {
            result.errorNo = 201;
            result.errorMsg = getLastErrMsg();
            return result;
        }
        result.fileSize = fileSize;

        Label label = getLabelById(labelId);
        if (label == null) {
            result.errorNo = 202;
            result.errorMsg = getLastErrMsg();
            return result;
        }

        LabelingOptions labelingOptions = getLabelingOptions(AssignmentMethod.AUTO, comments, true);

        String ownerEmail = getOrgFileInfo(handler, email, result);

        ProtectionSettings protectionSettings = new ProtectionSettings();
        protectionSettings.setPFileExtensionBehavior(PFileExtensionBehavior.Default);
        if (!Objects.equals(ownerEmail, "")) {
            protectionSettings.setDelegatedUserEmail(ownerEmail);
        }

        try {
            handler.setLabel(label, labelingOptions, protectionSettings);
            if (templateId != "") {
                ProtectionDescriptor protectionDescriptor = new ProtectionDescriptor(templateId);
                handler.setProtection(protectionDescriptor, protectionSettings);
            }
        }
        catch (Exception ex) {
            result.errorNo = 203;
            result.errorMsg = ex.getMessage();
            setError(53, "FileManager::SetLabel Failed.", ex.getMessage());
            return result;
        }

        boolean isCommitted = commitAsync(handler, actualFileName, result);
        if (isCommitted) {
            //handler.NotifyCommitSuccessful(fileName);
            result.newFileSize = getFileSize(actualFileName);
            result.newFileLabelGuid = labelId;
            result.newFileName = actualFileName;
            result.newFileOwner = ownerEmail;
            result.newFileTemplateGuid = labelId;
        }
        else {
            result.errorNo = 204;
            result.errorMsg = "AIP File CommitAsync Failed.";
            setError(53, "FileManager::SetLabel Failed.", "Label Id: " + labelId + ", SetLabel Failed.");
        }
        getActionFileInfo(actualFileName, result);
        return result;
    }

    public SetFileInfo deleteLabel(String fileName, String actualFileName, String email, String comments, boolean isDelProtection) {
        String outFileName = actualFileName.isEmpty() ? fileName : actualFileName;
        IFileHandler handler = createFileHandler(fileName, outFileName);
        return deleteLabel(handler, actualFileName, email, comments, isDelProtection, getFileSize(fileName));
    }
    public SetFileInfo deleteLabel(IStream fileStream, String actualFileName, String email, String comments, boolean isDelProtection) {
        IFileHandler handler = createFileHandler(fileStream, actualFileName);
        return deleteLabel(handler, actualFileName, email, comments, isDelProtection, fileStream.size());
    }
    public SetFileInfo deleteLabel(IFileHandler handler, String actualFileName, String email, String comments, boolean isDelProtection, long fileSize) {
        SetFileInfo result = new SetFileInfo();
        if (handler == null) {
            result.errorNo = 201;
            result.errorMsg = getLastErrMsg();
            return result;
        }
        result.fileSize = fileSize;

        LabelingOptions invokeLabelingOptions = getLabelingOptions(AssignmentMethod.PRIVILEGED, comments, true);

        String ownerEmail = getOrgFileInfo(handler, email, result);

        ProtectionSettings protectionSettings = new ProtectionSettings();
        protectionSettings.setPFileExtensionBehavior(PFileExtensionBehavior.Default);
        if (!Objects.equals(ownerEmail, "")) {
            protectionSettings.setDelegatedUserEmail(ownerEmail);
        }

        try {
            if (isDelProtection && handler.getProtection() != null) {
                if (handler.getProtection().getAccessCheck(Rights.Extract) || handler.getProtection().getAccessCheck(Rights.Owner)) {
                    handler.removeProtection();
                }
            }
            handler.deleteLabel(invokeLabelingOptions);
        }
        catch (Exception ex) {
            result.errorNo = 203;
            result.errorMsg = ex.getMessage();
            setError(53, "FileManager::DeleteLabel Failed.", ex.getMessage());
            return result;
        }

        boolean isCommited = commitAsync(handler, actualFileName, result);
        if (isCommited) {
            //handler.NotifyCommitSuccessful(fileName);
            result.newFileSize = getFileSize(actualFileName);
            result.newFileLabelGuid = "";
            result.newFileName = actualFileName;
            result.newFileOwner = ownerEmail;
            result.newFileTemplateGuid = "";
        }
        else {
            result.errorNo = 204;
            result.errorMsg = "AIP File CommitAsync Failed.";
            setError(54, "FileManager::DeleteLabel Failed.", "DeleteLabel Failed by " + ownerEmail);
        }
        getActionFileInfo(actualFileName, result);
        return result;
    }

    public SetFileInfo setProtection(String fileName, String actualFileName, String email, String templateId, String comments) {
        IFileHandler handler = createFileHandler(fileName, actualFileName);
        return setProtection(handler, actualFileName, email, templateId, comments, getFileSize(fileName));
    }
    public SetFileInfo setProtection(IStream fileStream, String actualFileName, String email, String templateId, String comments) {
        IFileHandler handler = createFileHandler(fileStream, actualFileName);
        return setProtection(handler, actualFileName, email, templateId, comments, fileStream.size());
    }
    public SetFileInfo setProtection(IFileHandler handler, String actualFileName, String email, String templateId, String comments, long fileSize) {
        SetFileInfo result = new SetFileInfo();
        if (handler == null) {
            result.errorNo = 201;
            result.errorMsg = getLastErrMsg();
            return result;
        }
        result.fileSize = fileSize;

        String ownerEmail = getOrgFileInfo(handler, email, result);

        try {
            ProtectionDescriptor protectionDescriptor = new ProtectionDescriptor(templateId);
            ProtectionSettings protectionSettings = new ProtectionSettings();
            protectionSettings.setPFileExtensionBehavior(PFileExtensionBehavior.Default);
            if (!Objects.equals(ownerEmail, "")) {
                protectionSettings.setDelegatedUserEmail(ownerEmail);
            }
            handler.setProtection(protectionDescriptor, protectionSettings);
        }
        catch (Exception ex) {
            result.errorNo = 204;
            result.errorMsg = ex.getMessage();
            setError(54, "FileManager::SetProtect Failed.", ex.getMessage());
            return result;
        }

        boolean isCommited = commitAsync(handler, actualFileName, result);
        if (isCommited) {
            //handler.NotifyCommitSuccessful(fileName);
            result.newFileSize = getFileSize(actualFileName);
            //result.newFileLabelGuid = string.Empty;
            result.newFileName = actualFileName;
            result.newFileOwner = ownerEmail;
            result.newFileTemplateGuid = "";
        }
        else {
            result.errorNo = 206;
            result.errorMsg = "AIP File CommitAsync Failed.";
            setError(56, "FileManager::SetProtect Failed.", "Template Id: " + templateId + ", SetProtect Failed.");
        }
        getActionFileInfo(actualFileName, result);
        return result;
    }

    public SetFileInfo removeProtection(String fileName, String actualFileName, String email, String comments) {
        IFileHandler handler = createFileHandler(fileName, actualFileName);
        return removeProtection(handler, actualFileName, email, comments, getFileSize(fileName));
    }
    public SetFileInfo removeProtection(IStream fileStream, String actualFileName, String email, String comments) {
        IFileHandler handler = createFileHandler(fileStream, actualFileName);
        return removeProtection(handler, actualFileName, email, comments, fileStream.size());
    }
    public SetFileInfo removeProtection(IFileHandler handler, String actualFileName, String email, String comments, long fileSize) {
        SetFileInfo result = new SetFileInfo();
        if (handler == null) {
            result.errorNo = 201;
            result.errorMsg = getLastErrMsg();
            return result;
        }
        result.fileSize = fileSize;
        String ownerEmail = getOrgFileInfo(handler, email, result);

        ProtectionSettings protectionSettings = new ProtectionSettings();
        protectionSettings.setPFileExtensionBehavior(PFileExtensionBehavior.Default);
        if (!Objects.equals(ownerEmail, "")) {
            protectionSettings.setDelegatedUserEmail(ownerEmail);
        }

        try {
            if (handler.getProtection() != null) {
                // 원본 파일 형식이 레이블 지정을 지원하지 않는 경우 보호를 제거하면 레이블이 손실됩니다.
                // 기본 형식이 레이블 지정을 지원하는 경우 레이블 메타데이터가 유지됩니다.
                if (handler.getProtection().getAccessCheck(Rights.Extract) || handler.getProtection().getAccessCheck(Rights.Owner)) {
                    handler.removeProtection();
                }
                //Use the GetTemporaryDecryptedStream() or GetTemporaryDecryptedFile() API to create a temp decrypted output to render in your application.
            }
            else {
                result.errorNo = 209;
                result.errorMsg = "파일에 암호화 정보가 없습니다.";
                return result;
            }
        }
        catch (Exception ex) {
            result.errorNo = 208;
            result.errorMsg = "FileManager::RemoveProtection Failed." + ex.getMessage();
            setError(59, "FileManager::RemoveProtection Failed.", ex.getMessage());
            return result;
        }

        boolean isCommited = commitAsync(handler, actualFileName, result);
        if (isCommited) {
            //handler.NotifyCommitSuccessful(fileName);
            result.newFileSize = getFileSize(actualFileName);
            result.newFileLabelGuid = "";
            result.newFileName = actualFileName;
            result.newFileOwner = ownerEmail;
            result.newFileTemplateGuid = "";
        }
        else {
            result.errorNo = 204;
            result.errorMsg = "AIP File CommitAsync Failed.";
            setError(53, "FileManager::RemoveProtection Failed.", "RemoveProtection Failed by " + ownerEmail);
        }
        getActionFileInfo(actualFileName, result);
        return result;
    }

    private LabelingOptions getLabelingOptions(AssignmentMethod assignmentMethod, String justificationMessage, boolean isDowngradeJustified) {
        LabelingOptions labelingOptions = new LabelingOptions();
        labelingOptions.setAssignmentMethod(assignmentMethod);
        labelingOptions.setJustificationMessage(justificationMessage);
        labelingOptions.setDowngradeJustified(isDowngradeJustified);
        return labelingOptions;
    }
    private String getOrgFileInfo(IFileHandler handler, String email, SetFileInfo result) {
        String ownerEmail = email;
        if (handler.getLabel() != null && handler.getLabel().label != null) {
            result.labelGuid = handler.getLabel().label.getId();
        }
        if (handler.getProtection() != null) {
            ownerEmail = handler.getProtection().getOwner();
            result.fileOwner = handler.getProtection().getOwner();
            if (handler.getProtection().getProtectionDescriptor() != null) {
                result.templateGuid = handler.getProtection().getProtectionDescriptor().getTemplateId();
            }
        }
        return ownerEmail;
    }

    private void getActionFileInfo(String actualFileName, SetFileInfo result) {
        AipFileInfo info = getFileInfo(actualFileName);
        if (info != null) {
            if (info.getLabel() != null) {
                result.newFileLabelGuid = info.getLabel().getId();
            }
            if (info.getProtection() != null) {
                result.newFileOwner = info.getProtection().getOwner();
                if (info.getProtection().getProtectionDescriptor() != null)
                {
                    result.newFileTemplateGuid = info.getProtection().getProtectionDescriptor().getTemplateId();
                }
            }
        }
    }

    private boolean commitAsync(IFileHandler handler, String actualFileName, SetFileInfo result) {
        boolean isCommitted = false;
        if (handler.isModified()) {
            Future<Boolean> future = handler.commitAsync(actualFileName);
            try {
                isCommitted = future.get();
            } catch (InterruptedException e) {
                result.errorNo = 301;
                result.errorMsg = "AIP File CommitAsync InterruptedException Exception.";
                setError(61, "FileManager::commitAsync Failed.", e.getMessage());
                Thread.currentThread().interrupt();
            } catch (ExecutionException e) {
                result.errorNo = 302;
                result.errorMsg = "AIP File CommitAsync ExecutionException Exception.";
                setError(62, "FileManager::commitAsync Failed.", e.getMessage());
            }
        }
        return isCommitted;
    }

//    public async Task<Stream> GetDecryptedStreamAsync(Stream inputStream, string filename)
//    {
//        var handler = CreateFileHandler(inputStream, filename);
//        return await handler.GetDecryptedTemporaryStreamAsync();
//    }
    // Protect the input bytes.
    public byte[] protect(IProtectionHandler handler, byte[] data) {
        long bufferSize = handler.getProtectedContentLength(data.length, true);
        byte[] outputBuffer = new byte[(int) bufferSize];

        handler.encryptBuffer(0, data, outputBuffer, true);
        return outputBuffer;
    }

    public byte[] unProtect(IProtectionHandler handler, byte[] data) {
        long buffersize = data.length;
        byte[] clearBuffer = new byte[(int) buffersize];

        long bytesDecrypted = handler.decryptBuffer(0, data, clearBuffer, true);

        byte[] outputBuffer = new byte[(int) bytesDecrypted];
        System.arraycopy(clearBuffer, 0, outputBuffer, 0, (int)bytesDecrypted);
//        for (int i = 0; i < bytesDecrypted; i++) {
//            outputBuffer[i] = clearBuffer[i];
//        }
        return outputBuffer;
    }

}
