/*
    This file is part of Kleopatra, the KDE keymanager
    SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
    SPDX-FileContributor: Intevation GmbH

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "card.h"

#include "readerstatus.h"

#include "kleopatra_debug.h"

#include <KLocalizedString>

using namespace Kleo;
using namespace Kleo::SmartCard;

namespace
{
static QString formatVersion(int value)
{
    if (value < 0) {
        return QString();
    }

    const unsigned int a = ((value >> 24) & 0xff);
    const unsigned int b = ((value >> 16) & 0xff);
    const unsigned int c = ((value >> 8) & 0xff);
    const unsigned int d = ((value) & 0xff);
    if (a) {
        return QStringLiteral("%1.%2.%3.%4").arg(QString::number(a), QString::number(b), QString::number(c), QString::number(d));
    } else if (b) {
        return QStringLiteral("%1.%2.%3").arg(QString::number(b), QString::number(c), QString::number(d));
    } else if (c) {
        return QStringLiteral("%1.%2").arg(QString::number(c), QString::number(d));
    }
    return QString::number(d);
}
}

Card::Card()
{
}

Card::~Card()
{
}

Card *Card::clone() const
{
    return new Card{*this};
}

void Card::setAppType(AppType app)
{
    mAppType = app;
}

AppType Card::appType() const
{
    return mAppType;
}

void Card::setStatus(Status s)
{
    mStatus = s;
}

Card::Status Card::status() const
{
    return mStatus;
}

void Card::setSerialNumber(const std::string &sn)
{
    mSerialNumber = sn;
}

std::string Card::serialNumber() const
{
    return mSerialNumber;
}

QString Card::displaySerialNumber() const
{
    return mDisplaySerialNumber;
}

void Card::setDisplaySerialNumber(const QString &serialNumber)
{
    mDisplaySerialNumber = serialNumber;
}

std::string Card::appName() const
{
    return mAppName;
}

void Card::setAppName(const std::string &name)
{
    mAppName = name;
}

void Card::setAppVersion(int version)
{
    mAppVersion = version;
}

int Card::appVersion() const
{
    return mAppVersion;
}

QString Card::displayAppVersion() const
{
    return formatVersion(mAppVersion);
}

void Card::setManufacturer(const std::string &value)
{
    if (!manufacturer().empty()) {
        qCDebug(KLEOPATRA_LOG) << "Card manufacturer is already set; overwriting existing value";
        mCardInfo.erase("MANUFACTURER");
    }
    mCardInfo.insert({"MANUFACTURER", value});
}

std::string Card::manufacturer() const
{
    return cardInfo("MANUFACTURER");
}

std::string Card::cardType() const
{
    return mCardType;
}

int Card::cardVersion() const
{
    return mCardVersion;
}

QString Card::displayCardVersion() const
{
    return formatVersion(mCardVersion);
}

QString Card::cardHolder() const
{
    return mCardHolder;
}

QString Card::publicKeyUrl() const
{
    return QString::fromStdString(cardInfo("PUBKEY-URL"));
}

void Card::setSigningKeyRef(const std::string &keyRef)
{
    mSigningKeyRef = keyRef;
}

std::string Card::signingKeyRef() const
{
    return mSigningKeyRef;
}

bool Card::hasSigningKey() const
{
    return !keyInfo(mSigningKeyRef).grip.empty();
}

void Card::setEncryptionKeyRef(const std::string &keyRef)
{
    mEncryptionKeyRef = keyRef;
}

std::string Card::encryptionKeyRef() const
{
    return mEncryptionKeyRef;
}

bool Card::hasEncryptionKey() const
{
    return !keyInfo(mEncryptionKeyRef).grip.empty();
}

void Card::setAuthenticationKeyRef(const std::string &keyRef)
{
    mAuthenticationKeyRef = keyRef;
}

std::string Card::authenticationKeyRef() const
{
    return mAuthenticationKeyRef;
}

bool Card::hasAuthenticationKey() const
{
    return !keyInfo(mAuthenticationKeyRef).grip.empty();
}

std::vector<Card::PinState> Card::pinStates() const
{
    return mPinStates;
}

void Card::setPinStates(const std::vector<PinState> &pinStates)
{
    mPinStates = pinStates;
}

bool Card::hasNKSNullPin() const
{
    static const std::string forceNullPinSerialNumber = qgetenv("KLEO_FORCE_NULLPIN").toStdString();

    if (mAppType != AppType::NetKeyApp) {
        return false;
    }
    if (serialNumber() == forceNullPinSerialNumber) {
        return true;
    }

    if (mPinStates.size() < 2) {
        qCWarning(KLEOPATRA_LOG) << "Invalid size of pin states:" << mPinStates.size();
        return false;
    }
    return mPinStates[0] == Card::NullPin;
}

bool Card::hasSigGNullPin() const
{
    if (mAppType != AppType::NetKeyApp) {
        return false;
    }

    if (mPinStates.size() < 4) {
        qCWarning(KLEOPATRA_LOG) << "Invalid size of pin states:" << mPinStates.size();
        return false;
    }
    return mPinStates[2] == Card::NullPin;
}

bool Card::operator==(const Card &other) const
{
    return mStatus == other.mStatus && mSerialNumber == other.mSerialNumber && mAppName == other.mAppName && mAppVersion == other.mAppVersion
        && mCardType == other.mCardType && mCardVersion == other.mCardVersion && mCardHolder == other.mCardHolder && mSigningKeyRef == other.mSigningKeyRef
        && mEncryptionKeyRef == other.mEncryptionKeyRef && mAuthenticationKeyRef == other.mAuthenticationKeyRef && mPinStates == other.mPinStates
        && mErrMsg == other.mErrMsg && mKeyInfos == other.mKeyInfos && mCardInfo == other.mCardInfo && mPinCounters == other.mPinCounters
        && mPinLabels == other.mPinLabels && mAppType == other.mAppType;
}

bool Card::operator!=(const Card &other) const
{
    return !operator==(other);
}

void Card::setErrorMsg(const QString &msg)
{
    mErrMsg = msg;
}

QString Card::errorMsg() const
{
    return mErrMsg;
}

void Card::setInitialKeyInfos(const std::vector<KeyPairInfo> &infos)
{
    mKeyInfos = infos;
}

const std::vector<KeyPairInfo> &Card::keyInfos() const
{
    return mKeyInfos;
}

const KeyPairInfo &Card::keyInfo(const std::string &keyRef) const
{
    static const KeyPairInfo nullKey;
    for (const KeyPairInfo &k : mKeyInfos) {
        if (k.keyRef == keyRef) {
            return k;
        }
    }
    return nullKey;
}

std::vector<int> Card::pinCounters() const
{
    return mPinCounters;
}

QStringList Card::pinLabels() const
{
    return mPinLabels;
}

void Card::setCardInfo(const std::vector<std::pair<std::string, std::string>> &infos)
{
    qCDebug(KLEOPATRA_LOG) << "Card" << serialNumber().c_str() << "info:";
    for (const auto &pair : infos) {
        qCDebug(KLEOPATRA_LOG) << pair.first.c_str() << ":" << pair.second.c_str();
        parseCardInfo(pair.first, pair.second);
    }
}

namespace
{
static int parseHexEncodedVersionTuple(const std::string &s)
{
    // s is a hex-encoded, unsigned int-packed version tuple,
    // i.e. each byte represents one part of the version tuple
    bool ok;
    const auto version = QByteArray::fromStdString(s).toUInt(&ok, 16);
    return ok ? version : -1;
}

static auto parseIntegerValues(const std::string &s)
{
    const auto strings = QByteArray::fromStdString(s).simplified().split(' ');
    std::vector<int> values;
    values.reserve(strings.size());
    std::ranges::transform(strings, std::back_inserter(values), [](const auto &s) {
        return s.toInt();
    });
    return values;
}
}

void Card::parseCardInfo(const std::string &name, const std::string &value)
{
    if (name == "APPVERSION") {
        mAppVersion = parseHexEncodedVersionTuple(value);
    } else if (name == "CARDTYPE") {
        mCardType = value;
    } else if (name == "CARDVERSION") {
        mCardVersion = parseHexEncodedVersionTuple(value);
    } else if (name == "DISP-NAME") {
        auto list = QString::fromUtf8(QByteArray::fromStdString(value)).split(QStringLiteral("<<"), Qt::SkipEmptyParts);
        std::reverse(list.begin(), list.end());
        mCardHolder = list.join(QLatin1Char(' ')).replace(QLatin1Char('<'), QLatin1Char(' '));
    } else if (name == "KEYPAIRINFO") {
        const KeyPairInfo info = KeyPairInfo::fromStatusLine(value);
        if (info.grip.empty()) {
            qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO status line" << QString::fromStdString(value);
            setStatus(Card::CardError);
        } else {
            updateKeyInfo(info);
        }
    } else if (name == "KEY-FPR") {
        // handle OpenPGP key fingerprints
        const auto values = QString::fromStdString(value).split(QLatin1Char(' '));
        if (values.size() < 2) {
            qCWarning(KLEOPATRA_LOG) << "Invalid KEY-FPR status line" << QString::fromStdString(value);
            setStatus(Card::CardError);
        }
        const auto keyNumber = values[0];
        const std::string keyRef = "OPENPGP." + keyNumber.toStdString();
        const auto fpr = values[1].toStdString();
        if (keyNumber == QLatin1Char('1') || keyNumber == QLatin1Char('2') || keyNumber == QLatin1Char('3')) {
            addCardInfo("KLEO-FPR-" + keyRef, fpr);
        } else {
            // Maybe more keyslots in the future?
            qCDebug(KLEOPATRA_LOG) << "Unhandled keyslot" << keyNumber;
        }
    } else if (name == "MANUFACTURER") {
        // the value of MANUFACTURER is the manufacturer ID as unsigned number
        // optionally followed by the name of the manufacturer, e.g.
        // 6 Yubico
        // 65534 unmanaged S/N range
        // for PKCS#15 cards the manufacturer ID is always 0, e.g.
        // 0 www.atos.net/cardos [R&S]
        const auto startOfManufacturerName = value.find(' ');
        if (startOfManufacturerName != std::string::npos) {
            addCardInfo(name, value.substr(startOfManufacturerName + 1));
        }
    } else if (name == "CHV-STATUS") {
        mPinCounters = parseIntegerValues(value);
        if (mAppName == "openpgp") {
            // for OpenPGP cards the PIN retry counters are the last 3 (of 7) integers
            if (mPinCounters.size() == 7) {
                mPinCounters.erase(mPinCounters.begin(), mPinCounters.begin() + 4);
                if (mPinLabels.empty()) {
                    mPinLabels = {
                        i18nc("@label PIN to unlock the smart card for user operations", "PIN"),
                        i18nc("@label PIN/Key to unblock/reset the normal PIN", "PUK"),
                        i18nc("@label PIN to unlock the smart card for administrative operations", "Admin PIN"),
                    };
                }
            } else {
                qCDebug(KLEOPATRA_LOG) << "Invalid CHV-STATUS value. Expected 7 integers, but got" << value;
                mPinCounters.clear();
            }
        }
        qCDebug(KLEOPATRA_LOG) << "PIN counters:" << mPinCounters;
    } else if (name == "CHV-LABEL") {
        mPinLabels = QString::fromStdString(value).split(u' ', Qt::SkipEmptyParts);
        qCDebug(KLEOPATRA_LOG) << "PIN labels:" << mPinLabels;
    } else {
        mCardInfo.insert({name, value});
    }
    if (!mPinLabels.empty() && !mPinCounters.empty() && (static_cast<int>(mPinLabels.size()) != static_cast<int>(mPinCounters.size()))) {
        qCDebug(KLEOPATRA_LOG) << "Number of PIN labels does not match number of PIN counters. Clearing labels.";
        mPinLabels.clear();
    }
}

void Card::addCardInfo(const std::string &name, const std::string &value)
{
    mCardInfo.insert({name, value});
}

std::string Card::cardInfo(const std::string &name) const
{
    const auto range = mCardInfo.equal_range(name);
    return range.first != range.second ? range.first->second : std::string();
}

void Card::updateKeyInfo(const KeyPairInfo &keyPairInfo)
{
    for (KeyPairInfo &k : mKeyInfos) {
        if (k.keyRef == keyPairInfo.keyRef) {
            k.update(keyPairInfo);
            return;
        }
    }
    mKeyInfos.push_back(keyPairInfo);
}

std::string Card::keyFingerprint(const std::string &keyRef) const
{
    return cardInfo("KLEO-FPR-" + keyRef);
}

void Card::setDisplayAppName(const QString &displayAppName)
{
    mDisplayAppName = displayAppName;
}

QString Card::displayAppName() const
{
    return mDisplayAppName;
}

std::string Card::certificateData(const std::string &keyRef) const
{
    return cardInfo("KLEO-CERTIFICATE-" + keyRef);
}

void Card::setCertificateData(const std::string &keyRef, const std::string &data)
{
    addCardInfo("KLEO-CERTIFICATE-" + keyRef, data);
}
