/*
 *    csv2usersDB.java
 *    Copyright (C) 2026 New Zealand Digital Library, http://www.nzdl.org
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.greenstone.gsdl3.util;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.charset.StandardCharsets;
import java.io.FileReader;
import java.io.Reader;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.apache.commons.csv.CSVRecord;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;

import org.greenstone.gsdl3.util.AuthenticationHelper;

/** 
    To run this from the command-line, first make sure that the derby networked server is running (ant start-derby),
    then run:

    Then use the wrapper script csv2usersDB.pl, found in greenstone3/bin/script.

    Don't forget to stop the networked derby server again at the end, if you had started it: ant stop-derby

*/
public class csv2usersDB
{

  public static void main(String[] args) throws SQLException
  {
    boolean appending = false;

    //System.out.println(CSVFormat.class.getProtectionDomain().getCodeSource());
    String usage = "Usage: java -Dgsdl3.writeablehome=\"$ENV{'GSDL3HOME'}\" org.greenstone.gsdl3.util.csv2usersDB full_path_of_the_csv_file usersDB [-append]";
    if (args.length < 2)
    {
      System.out.println(usage);
      System.exit(0);
    }
    String csv_filepath = args[0];
    String db_name = args[1];
    File txtfile = new File(csv_filepath);
    if (!txtfile.exists())
    {
      System.out.println("File " + csv_filepath + " does not exist.");
      System.out.println(usage);
      System.exit(0);
    }

    try
    {

      HashMap<String, String> all_passwords = new HashMap<String, String>();;
      DerbyWrapper dw = new DerbyWrapper(db_name);

      if (args.length > 2 && args[2].equals("-append"))
      {
        System.err.println("appending");
        appending = true;
      }
      else
      {
        // no appending, replace existing database: the text file 
        // represents the new database, so delete the existing DB first
        // with one exception - we may need to keep existing passwords, so
        // gather that data first
        UserQueryResult poo = dw.listAllUsers();
        UserQueryResult result = dw.listAllPasswords();
        if (result != null) {
          Vector <UserTermInfo> users = result.getUserTerms();
          for (int i = 0; i < users.size(); i++) {
            UserTermInfo user = users.elementAt(i);
            String uname = user.getUsername();
            String pword = user.getPassword();
            all_passwords.put(uname, pword);
          }
        }
        boolean delete_rows = dw.deleteAllUser();
        //  dw.closeDatabase();
        if (!delete_rows)
        {
          System.out.println("Couldn't delete rows of the users table");
          System.exit(0);
        }
      }

      String username = "";
      String password = "";
      String groups = "";
      String status = "true"; // default is true
      String comment = "";
      String email = "";

      Reader in = Files.newBufferedReader(Path.of(csv_filepath), StandardCharsets.UTF_8);

      // Strip BOM manually if present
      in.mark(1);
      if (in.read() != 0xFEFF) {
        in.reset();
      }
      
//      Iterable<CSVRecord> records = CSVFormat.DEFAULT.builder()
      CSVParser parser = CSVFormat.DEFAULT.builder()
      .setHeader()
        .setSkipHeaderRecord(true)
        .get()
        .parse(in);

      // check which fields are in the CSV
      Map<String, Integer> headers = parser.getHeaderMap();
      if (!headers.containsKey("username")) {
        System.err.println("Error: no username field found!");
        System.exit(0);
      }
      boolean has_password = headers.containsKey("password");
      boolean has_groups = headers.containsKey("groups");
      boolean has_status = headers.containsKey("status");
      boolean has_comment = headers.containsKey("comment");
      boolean has_email = headers.containsKey("email");
      
      for (CSVRecord record : parser) {
        
        username = record.get("username");
        if (username == null) {
          System.err.println("Error, no username specified for row, ignoring");
          continue;
        }
        if (has_password) password = record.get("password");
        if (has_groups) groups = record.get("groups");
        if (has_status) status = record.get("status");
        if (has_comment) comment = record.get("comment");
        if (has_email) email = record.get("email");

        // check if it's a new user or already exists in the database
        UserQueryResult findUserResult = dw.findUser(username);
        
        if (findUserResult == null)
        { // add new user
          if (password.equals("")) {
            String pw = all_passwords.get(username) ;
            if (pw != null && !pw.equals("")) {
              password = pw;
            } else {
              System.err.println("Error: no password specified for "+username+", and no existing password in the DB, ignoring that line");
              continue;
            }
          } else {
            // it came from the CSV, encrypt it
            password = AuthenticationHelper.hashPassword(password);
          }
          dw.addUser(username, password, UserTermInfo.expandGroups(groups), status, comment, email);
        }

        else
        { // modify existing user
          // if any of the other fields are not specified, get them from the database
          UserTermInfo user = findUserResult.getUserTerms().get(0);
          
          if (password.equals(""))
          { 
            password = user.getPassword();
          }
          else
          { // need to first encrypt (hash-and-hex) the user-entered password
            // Use the same encryption technique used by the Admin Authentication page
            // This ensures that the password generated for a string remains consistent
            password = AuthenticationHelper.hashPassword(password);
          }
          
          // groups should be expandedGroups because we no longer store the groups in userDB
          // as user-entered or compacted, but as programmatically expanded.
          // This allows HttpServletRequest.isUserInRole() to now automatically retrieve the
          // expandedGroups list of a user to check collectionConfig.xml security elements against.
          
          groups = has_groups ? UserTermInfo.expandGroups(groups) : user.getExpandedGroups();
          status = has_status ? status : user.getAccountStatus();
          comment = has_comment ? comment : user.getComment();
          email = has_email ? email : user.getEmail();
            //System.err.println("**** Password: " + password);				
            //System.err.println("**** " + username + " " + password + " " + groups + " " + accountstatus + " " + comment + " " + email);
          dw.modifyUserInfo(username, password, groups, status, comment, email);
        } // modify existing user

        username = "";
        password = "";
        groups = "";
        status = "true";
        comment = "";
        email = "";
        
      } // foreach record
    
    
      dw.closeDatabase();
      in.close();
    }
    catch (IOException e)
    {
    }
  }
}
