/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.wifi;

import android.util.Log;
import android.util.Xml;

import com.android.internal.util.FastXmlSerializer;
import com.android.server.wifi.util.XmlUtil;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Class used to backup/restore data.
 */
public class BackupRestoreController {
    private static final String TAG = "BackupRestoreController";

    private final WifiSettingsBackupRestore mWifiSettingsBackupRestore;
    private final Clock mClock;

    private static final String XML_TAG_DOCUMENT_HEADER = "WifiSettingsBackupData";
    private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");

    /**
     * Verbose logging flag.
     */
    private boolean mVerboseLoggingEnabled = false;
    /**
     * Store the dump of the backup/restore data for debugging. This is only stored when verbose
     * logging is enabled.
     */
    private byte[] mDebugLastBackupDataRetrieved;
    private long mLastBackupDataRetrievedTimestamp = 0;
    private byte[] mDebugLastBackupDataRestored;
    private long mLastBackupDataRestoredTimestamp = 0;

    public BackupRestoreController(WifiSettingsBackupRestore wifiSettingsBackupRestore,
            Clock clock) {
        mWifiSettingsBackupRestore = wifiSettingsBackupRestore;
        mClock = clock;
    }

    /**
     * Retrieve an XML byte stream representing the data that needs to be backed up.
     *
     * @return Raw byte stream of XML that needs to be backed up.
     */
    public byte[] retrieveBackupData() {
        try {
            final XmlSerializer out = new FastXmlSerializer();
            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
            XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);

            mWifiSettingsBackupRestore.retrieveBackupDataFromSettingsConfigStore(out, outputStream);

            XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
            byte[] backupData = outputStream.toByteArray();
            if (mVerboseLoggingEnabled) {
                mDebugLastBackupDataRetrieved = backupData;
            }
            mLastBackupDataRetrievedTimestamp = mClock.getWallClockMillis();
            return backupData;
        } catch (IOException e) {
            Log.e(TAG, "Error retrieving the backup data: " + e);
        }
        return new byte[0];
    }

    private void distpatchBackupData(String sectionName, XmlPullParser in, int depth)
            throws XmlPullParserException, IOException {
        switch (sectionName) {
            case WifiSettingsBackupRestore.XML_TAG_SECTION_HEADER_WIFI_SETTINGS_DATA:
                mWifiSettingsBackupRestore.restoreSettingsFromBackupData(in, depth);
                break;
            default:
                Log.i(TAG, "unknown tag: (backed up from newer version?)" + sectionName);
        }
    }

    /**
     * Split the back up data to retrieve each back up session.
     *
     * @param data raw byte stream representing the XML data.
     */
    public void parserBackupDataAndDispatch(byte[] data) {
        if (data == null || data.length == 0) {
            Log.e(TAG, "Invalid backup data received");
            return;
        }
        if (mVerboseLoggingEnabled) {
            mDebugLastBackupDataRestored = data;
        }
        try {
            final XmlPullParser in = Xml.newPullParser();
            ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
            in.setInput(inputStream, StandardCharsets.UTF_8.name());
            // Start parsing the XML stream.
            XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
            int sectionDepth = in.getDepth();
            String[] sectionName = new String[1];
            while (XmlUtil.gotoNextSectionOrEnd(in, sectionName, sectionDepth)) {
                try {
                    if (sectionName[0] == null) {
                        throw new XmlPullParserException("Missing value name");
                    }
                    distpatchBackupData(sectionName[0], in, sectionDepth);
                } catch (XmlPullParserException | IOException ex) {
                    Log.e(TAG, "Error to parser tag: " + sectionName[0]);
                }
            }
            mLastBackupDataRestoredTimestamp = mClock.getWallClockMillis();
        } catch (XmlPullParserException | IOException ex) {
            Log.e(TAG, "Error :" + ex);
        }

    }

    /**
     * Enable verbose logging.
     *
     * @param verboseEnabled whether or not verbosity log level is enabled.
     */
    public void enableVerboseLogging(boolean verboseEnabled) {
        mVerboseLoggingEnabled = verboseEnabled;
    }

    /**
     * Dump out the last backup/restore data if verbose logging is enabled.
     *
     * @param fd   unused
     * @param pw   PrintWriter for writing dump to
     * @param args unused
     */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("Dump of " + TAG);
        if (mDebugLastBackupDataRetrieved != null) {
            pw.println("Last backup data retrieved: "
                    + createLogFromBackupData(mDebugLastBackupDataRetrieved));

        }
        pw.println("mLastBackupDataRetrievedTimestamp: "
                + (mLastBackupDataRetrievedTimestamp != 0
                ? FORMATTER.format(new Date(mLastBackupDataRetrievedTimestamp)) : "N/A"));
        if (mDebugLastBackupDataRestored != null) {
            pw.println("Last backup data restored: "
                    + createLogFromBackupData(mDebugLastBackupDataRestored));
        }
        pw.println("mLastBackupDataRestoredTimestamp: "
                + (mLastBackupDataRestoredTimestamp != 0
                ? FORMATTER.format(new Date(mLastBackupDataRestoredTimestamp)) : "N/A"));
    }

    private String createLogFromBackupData(byte[] data) {
        if (data != null) {
            StringBuilder sb = new StringBuilder();
            try {
                String xmlString = new String(data, StandardCharsets.UTF_8.name());
                for (String line : xmlString.split("\n")) {
                    sb.append(line).append("\n");
                }
                return sb.toString();
            } catch (UnsupportedEncodingException e) {
                Log.e(TAG, "fail to create log from backup data. " + e);
            }
        }
        return "";
    }
}
