#include "add-user-panel.h"
#include "../PasswordLineEdit/password-line-edit.h"
#include "../MessageDialog/message-dialog.h"
#include "../DialogManager/dialog-manager.h"
#include "../ModalOverlay/modal-overlay-manager.h"
#include <QLabel>
#include <QFile>
#include <QTextStream>
#include <QKeyEvent>
#include <QDebug>

namespace {

QString suggestLoginFromFullName(const QString& fullName)
{
    QString s = fullName.trimmed();
    if (s.isEmpty())
        return {};

    // Best-effort ASCII-ish slug:
    // - normalize and strip combining marks
    // - keep only [a-z0-9_-]
    // - collapse whitespace to '-'
    s = s.normalized(QString::NormalizationForm_D);

    QString out;
    out.reserve(s.size());

    bool lastWasSep = false;
    for (const QChar& ch : s) {
        if (ch.category() == QChar::Mark_NonSpacing || ch.category() == QChar::Mark_SpacingCombining || ch.category() == QChar::Mark_Enclosing) {
            continue;
        }

        if (ch.isSpace()) {
            if (!out.isEmpty() && !lastWasSep) {
                out.append('-');
                lastWasSep = true;
            }
            continue;
        }

        const QChar lower = ch.toLower();
        const ushort u = lower.unicode();

        const bool isAsciiLetter = (u >= 'a' && u <= 'z');
        const bool isAsciiDigit = (u >= '0' && u <= '9');
        const bool isAllowedPunct = (u == '_' || u == '-');

        if (isAsciiLetter || isAsciiDigit || isAllowedPunct) {
            out.append(lower);
            lastWasSep = isAllowedPunct;
        }
    }

    while (out.endsWith('-') || out.endsWith('_')) out.chop(1);
    while (out.startsWith('-') || out.startsWith('_')) out.remove(0, 1);

    if (out.isEmpty())
        return {};

    // Ensure it starts with a letter or underscore per typical Linux username rules.
    const ushort first = out.at(0).unicode();
    const bool firstOk = (first == '_' || (first >= 'a' && first <= 'z'));
    if (!firstOk) {
        out.prepend('u');
    }

    if (out.size() > 32)
        out = out.left(32);

    return out;
}

}

AddUserPanel::AddUserPanel(QWidget* parent) : OverlayPanelBase(":/plugin/add-user-panel.qss", parent) {
    // Use base-provided containers: contentLayout() and bottomLayout()
    auto* contentLayout = this->contentLayout();
    auto* bottomLayout = this->bottomLayout();

    auto* form = new QFormLayout();
    form->setSpacing(12);
    form->setHorizontalSpacing(16);
    form->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);

    QFont fieldFont; fieldFont.setPixelSize(14); fieldFont.setWeight(QFont::Normal);

    m_uidEdit = new QLineEdit();
    m_uidEdit->setPlaceholderText(tr("Auto"));
    m_uidEdit->setFixedHeight(32); m_uidEdit->setFont(fieldFont);
    form->addRow(tr("User ID"), m_uidEdit);

    m_fullNameEdit = new QLineEdit();
    m_fullNameEdit->setPlaceholderText(tr("Enter"));
    m_fullNameEdit->setFixedHeight(32); m_fullNameEdit->setFont(fieldFont);
    form->addRow(tr("Full Name"), m_fullNameEdit);

    m_loginEdit = new QLineEdit();
    m_loginEdit->setPlaceholderText(tr("Enter"));
    m_loginEdit->setFixedHeight(32); m_loginEdit->setFont(fieldFont);
    form->addRow(tr("Login") + QStringLiteral(" *"), m_loginEdit);

    m_passwordEdit = new PasswordLineEdit();
    m_passwordEdit->setPlaceholderText(tr("Enter"));
    m_passwordEdit->setFixedHeight(32); m_passwordEdit->setFont(fieldFont);
    form->addRow(tr("Password") + QStringLiteral(" *"), m_passwordEdit);

    m_groupCombo = new QComboBox();
    m_groupCombo->setFixedHeight(32); m_groupCombo->setFont(fieldFont);
    m_groupCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    form->addRow(tr("Primary Group"), m_groupCombo);

    contentLayout->addLayout(form);

    // 使用基类创建统一的内联告警行：上下边距0，图标与文本间距6
    this->createInlineErrorRow(form, /*topBottomMargin=*/0, /*iconTextSpacing=*/8);


    auto* buttonLayout = new QHBoxLayout();
    buttonLayout->addStretch();
    m_cancelButton = new DialogButton(tr("Cancel"), DialogButton::Cancel);
    m_confirmButton = new DialogButton(tr("Add"), DialogButton::Confirm);
    m_confirmButton->setEnabled(false);
    buttonLayout->addWidget(m_cancelButton);
    buttonLayout->addWidget(m_confirmButton);
    bottomLayout->addLayout(buttonLayout);


    connect(m_cancelButton, &QToolButton::clicked, this, [this]{ emit requestClose(); });
    connect(m_confirmButton, &QToolButton::clicked, this, &AddUserPanel::onConfirm);
    // Validation: keep the submit button state updated live while typing,
    // but only show error messages on submit or when leaving a field.
    connect(m_uidEdit, &QLineEdit::textChanged, this, &AddUserPanel::validateInputs);
    connect(m_fullNameEdit, &QLineEdit::textChanged, this, &AddUserPanel::validateInputs);
    connect(m_loginEdit, &QLineEdit::textChanged, this, &AddUserPanel::validateInputs);
    connect(m_passwordEdit, &QLineEdit::textChanged, this, &AddUserPanel::validateInputs);

    connect(m_uidEdit, &QLineEdit::editingFinished, this, [this]() {
        validateInputsImpl(ValidateTrigger::BlurUid);
    });
    connect(m_loginEdit, &QLineEdit::editingFinished, this, [this]() {
        validateInputsImpl(ValidateTrigger::BlurLogin);
    });
    connect(m_passwordEdit, &QLineEdit::editingFinished, this, [this]() {
        validateInputsImpl(ValidateTrigger::BlurPassword);
    });

    // Once the user edits the login field manually, stop auto-generating it from Full Name.
    // Note: textEdited() only fires for user edits, not for setText() done by code.
    connect(m_loginEdit, &QLineEdit::textEdited, this, [this]() {
        m_loginManuallyEdited = true;
    });

    // Auto-generate login from Full Name until the user manually edits Login.
    connect(m_fullNameEdit, &QLineEdit::textChanged, this, [this](const QString&) {
        if (m_loginManuallyEdited)
            return;

        const QString suggested = suggestLoginFromFullName(m_fullNameEdit->text());
        if (suggested.isEmpty())
            return;

        // Only set when it would actually change; this avoids cursor jumps on redundant setText().
        if (m_loginEdit->text().trimmed() == suggested)
            return;

        // Ensure the suggestion respects the panel's login validation.
        if (!isValidLogin(suggested))
            return;

        m_loginEdit->setText(suggested);
    });

    connect(m_loginEdit, &QLineEdit::textChanged, this, [this]() {
        const QString login = m_loginEdit->text().trimmed();
        if (!login.isEmpty()) {
            if (m_groupCombo->count() > 0 && m_groupCombo->itemData(0).toString() == "__username__") {
                m_groupCombo->removeItem(0);
            }
            m_groupCombo->insertItem(0, login + tr(" (new, default)"));
            m_groupCombo->setItemData(0, "__username__");
            m_groupCombo->setCurrentIndex(0);
        } else {
            if (m_groupCombo->count() > 0 && m_groupCombo->itemData(0).toString() == "__username__") {
                m_groupCombo->removeItem(0);
            }
            int usersIndex = m_groupCombo->findText("users", Qt::MatchFixedString | Qt::MatchCaseSensitive);
            if (usersIndex < 0) {
                // Case-insensitive fallback (omit MatchCaseSensitive)
                usersIndex = m_groupCombo->findText("users", Qt::MatchFixedString);
            }
            if (usersIndex >= 0) {
                m_groupCombo->setCurrentIndex(usersIndex);
            }
        }
    });

    form->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
    for (int r = 0; r < form->rowCount(); ++r) {
        if (auto *item = form->itemAt(r, QFormLayout::LabelRole)) {
            if (auto *lbl = qobject_cast<QLabel*>(item->widget())) {
                lbl->setMinimumHeight(32);
                lbl->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
                QFont lf = lbl->font(); lf.setPixelSize(14); lf.setWeight(QFont::Normal); lbl->setFont(lf);
                lbl->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
            }
        }
    }

    loadGroups();
    loadNextAvailableUid();
    if (m_loginEdit->text().trimmed().isEmpty()) {
        int usersIndex = m_groupCombo->findText("users", Qt::MatchFixedString | Qt::MatchCaseSensitive);
        if (usersIndex < 0) usersIndex = m_groupCombo->findText("users", Qt::MatchFixedString);
        if (usersIndex >= 0) m_groupCombo->setCurrentIndex(usersIndex);
    }
    m_loginEdit->setFocus();
    // Inline error label already added above between form and buttons
}

void AddUserPanel::setInputsEnabled(bool enabled) {
    m_uidEdit->setEnabled(enabled);
    m_fullNameEdit->setEnabled(enabled);
    m_loginEdit->setEnabled(enabled);
    m_passwordEdit->setEnabled(enabled);
    m_groupCombo->setEnabled(enabled);
    m_confirmButton->setEnabled(enabled && m_confirmButton->isEnabled());
}

void AddUserPanel::showError(const QString& msg) {
    setInputsEnabled(true);
    m_state = Idle;
    // Runtime/process errors: popup only; validation stays inline.
    hideInlineError();
    DialogManager::instance().showError(tr("Add User Failed"), msg, this);
    // Re-run validation to restore confirm button when fields are still valid
    validateInputs();
    emit userCreateFailed(msg);
}

bool AddUserPanel::isValidLogin(const QString& login) const {
    static QRegularExpression re("^[a-z_][a-z0-9_-]*[$]?$", QRegularExpression::CaseInsensitiveOption);
    return re.match(login).hasMatch() && login.length() <= 32;
}

void AddUserPanel::loadGroups() {
    m_groupCombo->clear();
    QFile f("/etc/group");
    if (f.open(QFile::ReadOnly | QFile::Text)) {
        QTextStream ts(&f);
        while (!ts.atEnd()) {
            const QString line = ts.readLine();
            const QStringList parts = line.split(":");
            if (parts.size() >= 1) {
                const QString name = parts.at(0).trimmed();
                if (!name.isEmpty()) m_groupCombo->addItem(name);
            }
        }
    }
}

void AddUserPanel::loadNextAvailableUid() {
    int uidMin = 1000;
    int uidMax = 60000;
    QFile loginDefs("/etc/login.defs");
    if (loginDefs.open(QFile::ReadOnly | QFile::Text)) {
        QTextStream ts(&loginDefs);
        while (!ts.atEnd()) {
            const QString line = ts.readLine().trimmed();
            if (line.startsWith("UID_MIN")) {
                const QStringList parts = line.split(QRegularExpression("\\s+"));
                if (parts.size() >= 2) {
                    bool ok = false; int val = parts.at(1).toInt(&ok); if (ok && val > 0) uidMin = val;
                }
            } else if (line.startsWith("UID_MAX")) {
                const QStringList parts = line.split(QRegularExpression("\\s+"));
                if (parts.size() >= 2) {
                    bool ok = false; int val = parts.at(1).toInt(&ok); if (ok && val > 0) uidMax = val;
                }
            }
        }
    }

    int maxUid = uidMin - 1;
    QFile passwd("/etc/passwd");
    if (passwd.open(QFile::ReadOnly | QFile::Text)) {
        QTextStream ts(&passwd);
        while (!ts.atEnd()) {
            const QString line = ts.readLine();
            const QStringList parts = line.split(":");
            if (parts.size() >= 3) {
                bool ok = false; int uid = parts.at(2).toInt(&ok);
                if (ok && uid >= uidMin && uid <= uidMax && uid > maxUid) maxUid = uid;
            }
        }
    }
    const int nextUid = maxUid + 1;
    m_uidEdit->setText(QString::number(nextUid));
    m_uidEdit->setPlaceholderText(tr("Default: %1").arg(nextUid));
}

void AddUserPanel::validateInputs() {
    // Typing should not immediately show errors; only keep the confirm button state updated.
    validateInputsImpl(ValidateTrigger::Typing);
}

QString AddUserPanel::fieldErrorMessage(ValidateTrigger trigger) const
{
    const QString login = m_loginEdit->text().trimmed();
    const QString pwd = m_passwordEdit->text();
    const QString uidText = m_uidEdit->text().trimmed();

    const auto loginError = [&]() -> QString {
        if (login.isEmpty()) return tr("Login name is required");
        if (!isValidLogin(login)) return tr("Invalid login name");
        return {};
    };
    const auto passwordError = [&]() -> QString {
        if (pwd.isEmpty()) return tr("Password is required");
        if (pwd.length() < 6) return tr("Password must be at least 6 characters");
        return {};
    };
    const auto uidError = [&]() -> QString {
        if (uidText.isEmpty()) return {};
        bool okInt = false;
        const int uid = uidText.toInt(&okInt);
        if (!okInt || uid < 1000) return tr("UID must be a number >= 1000 or left empty");
        return {};
    };

    switch (trigger) {
    case ValidateTrigger::BlurLogin:
        return loginError();
    case ValidateTrigger::BlurPassword:
        return passwordError();
    case ValidateTrigger::BlurUid:
        return uidError();
    case ValidateTrigger::Submit:
        // On submit, show the first blocking error in a stable order.
        if (const auto e = loginError(); !e.isEmpty()) return e;
        if (const auto e = passwordError(); !e.isEmpty()) return e;
        if (const auto e = uidError(); !e.isEmpty()) return e;
        return {};
    case ValidateTrigger::Typing:
        return {};
    }

    return {};
}

void AddUserPanel::validateInputsImpl(ValidateTrigger trigger) {
    // Always update button enablement based on full-form validity.
    const QString login = m_loginEdit->text().trimmed();
    const QString pwd = m_passwordEdit->text();
    const QString uidText = m_uidEdit->text().trimmed();

    bool ok = true;
    if (login.isEmpty() || !isValidLogin(login)) ok = false;
    if (pwd.isEmpty() || pwd.length() < 6) ok = false;
    if (!uidText.isEmpty()) {
        bool okInt = false;
        const int uid = uidText.toInt(&okInt);
        if (!okInt || uid < 1000) ok = false;
    }
    m_confirmButton->setEnabled(ok && m_state == Idle);

    // Decide whether to show an error message.
    if (trigger == ValidateTrigger::Typing) {
        hideInlineError();
        return;
    }

    // After the user has attempted submit once, keep showing the current first blocking error
    // on subsequent blur events as well.
    ValidateTrigger effectiveTrigger = trigger;
    if (m_submitAttempted && trigger != ValidateTrigger::Submit) {
        effectiveTrigger = ValidateTrigger::Submit;
    }

    m_lastValidationError = fieldErrorMessage(effectiveTrigger);
    if (m_lastValidationError.isEmpty()) {
        hideInlineError();
        return;
    }

    showInlineError(m_lastValidationError);
}

QStringList AddUserPanel::buildUseraddArgs() const {
    QStringList args; args << "-m";
    int idx = m_groupCombo->currentIndex();
    const QString login = m_loginEdit->text().trimmed();
    if (idx >= 0 && m_groupCombo->itemData(idx).toString() == "__username__") {
        args << "-U";
    } else if (idx >= 0) {
        QString grp = m_groupCombo->currentText().trimmed();
        int suffixPos = grp.indexOf(" ("); if (suffixPos > 0) grp = grp.left(suffixPos);
        if (!grp.isEmpty() && grp != login) args << "-g" << grp; else args << "-U";
    } else {
        args << "-U";
    }
    const QString full = m_fullNameEdit->text().trimmed(); if (!full.isEmpty()) args << "-c" << full;
    const QString uid = m_uidEdit->text().trimmed(); if (!uid.isEmpty()) args << "-u" << uid;
    args << login;
    return args;
}

void AddUserPanel::onConfirm() {
    m_submitAttempted = true;
    validateInputsImpl(ValidateTrigger::Submit);
    if (!m_confirmButton->isEnabled()) return; // Bubble already displayed for validation errors
    setInputsEnabled(false);
    startUseradd();
}

void AddUserPanel::startUseradd() {
    if (m_proc) { m_proc->deleteLater(); m_proc = nullptr; }
    m_proc = new QProcess(this);
    m_state = CreatingUser;
    connect(m_proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &AddUserPanel::onUseraddFinished);
    connect(m_proc, &QProcess::errorOccurred, this, &AddUserPanel::onProcError);
    QStringList args; args << "/usr/sbin/useradd"; args << buildUseraddArgs();
    m_proc->start("pkexec", args);
}

void AddUserPanel::onUseraddFinished(int exitCode, QProcess::ExitStatus) {
    if (exitCode == 0) { startChpasswd(); return; }
    const QString err = QString::fromUtf8(m_proc->readAllStandardError());
    const QString out = QString::fromUtf8(m_proc->readAllStandardOutput());
    const QString fullMsg = (out + "\n" + err).trimmed();
    if (exitCode == 127) showError(tr("Authentication failed"));
    else if (fullMsg.contains("already exists", Qt::CaseInsensitive)) showError(tr("User already exists"));
    else if (fullMsg.contains("invalid", Qt::CaseInsensitive)) showError(tr("Invalid parameters for useradd"));
    else if (fullMsg.contains("Not authorized", Qt::CaseInsensitive)) showError(tr("Authentication failed"));
    else if (fullMsg.isEmpty()) showError(tr("Failed to create user (exit code %1). Authentication may have been cancelled.").arg(exitCode));
    else showError(tr("Failed to create user: %1").arg(fullMsg));
}

void AddUserPanel::startChpasswd() {
    if (m_proc) { m_proc->deleteLater(); m_proc = nullptr; }
    m_proc = new QProcess(this);
    m_state = SettingPassword;
    connect(m_proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &AddUserPanel::onChpasswdFinished);
    connect(m_proc, &QProcess::errorOccurred, this, &AddUserPanel::onProcError);
    QStringList args; args << "/usr/sbin/chpasswd";
    m_proc->start("pkexec", args);
    const QString line = m_loginEdit->text().trimmed() + ":" + m_passwordEdit->text();
    m_proc->write(line.toUtf8());
    m_proc->closeWriteChannel();
}

void AddUserPanel::onChpasswdFinished(int exitCode, QProcess::ExitStatus) {
    if (exitCode == 0) {
        emit userCreateSucceeded();
        emit requestClose();
        return;
    }
    const QString err = QString::fromUtf8(m_proc->readAllStandardError());
    const QString out = QString::fromUtf8(m_proc->readAllStandardOutput());
    const QString fullMsg = (out + "\n" + err).trimmed();
    if (exitCode == 127 || fullMsg.contains("Not authorized", Qt::CaseInsensitive)) showError(tr("Authentication failed"));
    else if (fullMsg.isEmpty()) showError(tr("Failed to set password (exit code %1)").arg(exitCode));
    else showError(tr("Failed to set password: %1").arg(fullMsg));
}

void AddUserPanel::onProcError(QProcess::ProcessError) {
    if (m_state == CreatingUser) showError(tr("Failed to run useradd (permission denied or missing command)"));
    else if (m_state == SettingPassword) showError(tr("Failed to run chpasswd (permission denied or missing command)"));
    else showError(tr("Operation failed"));
}
