unit frm_UsuariosEditor;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Buttons,
  Vcl.ComCtrls, Data.DB, FireDAC.Comp.Client, uAppTheme, FireDAC.Stan.Param, uBaseForm,
  Vcl.Grids, Vcl.DBGrids;

type
  TfrmUsuariosEditor = class(TfrmBase)
    lblUsername: TLabel;
    edtUsername: TEdit;
    lblNombreCompleto: TLabel;
    edtNombreCompleto: TEdit;
    lblPassword: TLabel;
    edtPassword: TEdit;
    lblEmail: TLabel;
    edtEmail: TEdit;
    lblRol: TLabel;
    cbRol: TComboBox;
    chkActivo: TCheckBox;
    chkAdminGlobal: TCheckBox;
    procedure FormShow(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnSalirClick(Sender: TObject);
    procedure btnSincronizarClick(Sender: TObject);
    procedure dbgItemsCellClick(Column: TColumn);
    procedure dbgItemsTitleClick(Column: TColumn);
    procedure dbgItemsKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  protected
    procedure DoAnadir; override;
    procedure DoModificar; override;
    procedure DoPrimero; override;
    procedure DoAnterior; override;
    procedure DoSiguiente; override;
    procedure DoUltimo; override;
  private
    FUserId: string;
    FParentForm: TForm;
    FAllLeer: Boolean;
    FAllEscribir: Boolean;
    FAllEliminar: Boolean;
    dsPermissions: TDataSource;
    qryPermissions: TFDQuery;
    procedure LoadData;
    procedure LoadPermissions;
    procedure SavePermissions;
    procedure ToggleAllField(const AFieldName: string; ANewState: Boolean);
    procedure UpdateTitleCaptions;
  public
    property UserId: string read FUserId write FUserId;
    property ParentForm: TForm read FParentForm write FParentForm;
  end;

var
  frmUsuariosEditor: TfrmUsuariosEditor;

implementation

uses
  dmg_Main, uDbErrorHandler, frm_Usuarios;

{$R *.dfm}

procedure TfrmUsuariosEditor.FormShow(Sender: TObject);
begin
  TAppTheme.ApplyToForm(Self);

  // Respetar mayúsculas y minúsculas exactamente según lo tecleado por el usuario
  edtUsername.CharCase := ecNormal;
  edtPassword.CharCase := ecNormal;

  btnGuardar.OnClick := btnSaveClick;
  btnSalir.OnClick := btnSalirClick;

  pgcDetails.Visible := True;
  TsListado.TabVisible := False;
  pgcDetails.ActivePage := tsDatosEnvio;
  
  // Habilitar y configurar grilla de permisos heredada
  dbgItems.Visible := (FUserId <> '');
  dbgItems.ReadOnly := False; // Habilitar interacción en la grilla
  dbgItems.Options := dbgItems.Options - [dgEditing]; // Deshabilitar editor de texto inplace
  dbgItems.OnCellClick := dbgItemsCellClick;
  dbgItems.OnTitleClick := dbgItemsTitleClick;
  dbgItems.OnKeyDown := dbgItemsKeyDown;
  dbgItems.OnDrawColumnCell := dbgItemsDrawColumnCell;
  
  pnlSideToolbar.Visible := (FUserId <> ''); 
  btnNuevoArticulo.Visible := True;
  btnNuevoArticulo.Caption := 'Sincronizar';
  btnNuevoArticulo.OnClick := btnSincronizarClick;
  btnEliminaArticulo.Visible := False;
  btnBuscarArticulo.Visible := False;
  
  pnlFooter.Visible := False;

  // Inicializar estado de toggle masivo
  FAllLeer := False;
  FAllEscribir := False;
  FAllEliminar := False;

  if not Assigned(dsPermissions) then
    dsPermissions := TDataSource.Create(Self);

  if not Assigned(qryPermissions) then
  begin
    qryPermissions := TFDQuery.Create(Self);
    qryPermissions.Connection := dmgMain.dbConn;
  end;

  dbgItems.DataSource := dsPermissions;
  dsPermissions.DataSet := qryPermissions;

  // Configurar columnas
  dbgItems.Columns.Clear;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'Empresa';
    Title.Caption := 'Empresa';
    Width := 150;
    ReadOnly := True;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'Modulo';
    Title.Caption := 'Módulo';
    Width := 180;
    ReadOnly := True;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'puede_leer';
    Title.Caption := #$2610 + ' Leer';
    Title.Alignment := taCenter;
    Alignment := taCenter;
    Width := 70;
    ReadOnly := True;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'puede_escribir';
    Title.Caption := #$2610 + ' Escribir';
    Title.Alignment := taCenter;
    Alignment := taCenter;
    Width := 80;
    ReadOnly := True;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'puede_eliminar';
    Title.Caption := #$2610 + ' Eliminar';
    Title.Alignment := taCenter;
    Alignment := taCenter;
    Width := 80;
    ReadOnly := True;
  end;

  if FUserId <> '' then
  begin
    LoadData;
    LoadPermissions;
  end
  else
  begin
    edtUsername.Clear;
    edtNombreCompleto.Clear;
    edtPassword.Clear;
    edtEmail.Clear;
    cbRol.ItemIndex := 3; // USER
    chkActivo.Checked := True;
    chkAdminGlobal.Checked := False;
  end;
end;

procedure TfrmUsuariosEditor.LoadData;
begin
  dmgMain.qryExec.Close;
  dmgMain.qryExec.SQL.Text := 'SELECT id, username, password_hash, nombre_completo, email, rol, activo, es_admin_global FROM ge_usuarios WHERE id = :id';
  dmgMain.qryExec.ParamByName('id').AsString := FUserId;
  dmgMain.qryExec.Open;
  
  if not dmgMain.qryExec.IsEmpty then
  begin
    edtUsername.Text := dmgMain.qryExec.FieldByName('username').AsString;
    edtNombreCompleto.Text := dmgMain.qryExec.FieldByName('nombre_completo').AsString;
    edtPassword.Text := dmgMain.qryExec.FieldByName('password_hash').AsString;
    edtEmail.Text := dmgMain.qryExec.FieldByName('email').AsString;
    
    cbRol.ItemIndex := cbRol.Items.IndexOf(dmgMain.qryExec.FieldByName('rol').AsString);
    if cbRol.ItemIndex = -1 then cbRol.ItemIndex := 3;
    
    chkActivo.Checked := dmgMain.qryExec.FieldByName('activo').AsInteger <> 0;
    chkAdminGlobal.Checked := dmgMain.qryExec.FieldByName('es_admin_global').AsInteger <> 0;
  end;
  dmgMain.qryExec.Close;
end;

procedure TfrmUsuariosEditor.LoadPermissions;
begin
  qryPermissions.Close;
  qryPermissions.SQL.Text := 
    'SELECT p.id, e.nombre as Empresa, m.nombre as Modulo, p.puede_leer, p.puede_escribir, p.puede_eliminar ' +
    'FROM ge_usuario_empresa_permisos p ' +
    'JOIN ge_empresas e ON p.empresa_id = e.id ' +
    'JOIN ge_modulos m ON p.modulo_id = m.id ' +
    'WHERE p.usuario_id = :uid';
  qryPermissions.ParamByName('uid').AsString := FUserId;
  
  // Configurar FireDAC para que solo actualice la tabla de permisos
  qryPermissions.UpdateOptions.UpdateTableName := 'ge_usuario_empresa_permisos';
  qryPermissions.UpdateOptions.KeyFields := 'id';
  
  qryPermissions.Open;

  // Forzar que los campos del JOIN sean editables en el dataset
  qryPermissions.FieldByName('puede_leer').ReadOnly := False;
  qryPermissions.FieldByName('puede_escribir').ReadOnly := False;
  qryPermissions.FieldByName('puede_eliminar').ReadOnly := False;
end;

procedure TfrmUsuariosEditor.SavePermissions;
var
  LBook: TBookmark;
  LQryUpdate: TFDQuery;
begin
  if not qryPermissions.Active or qryPermissions.IsEmpty then
    Exit;

  // Cancelar cualquier edición pendiente en el dataset
  if qryPermissions.State in [dsEdit, dsInsert] then
    qryPermissions.Post;

  LQryUpdate := TFDQuery.Create(nil);
  try
    LQryUpdate.Connection := dmgMain.dbConn;
    LBook := qryPermissions.GetBookmark;
    qryPermissions.DisableControls;
    try
      qryPermissions.First;
      while not qryPermissions.Eof do
      begin
        LQryUpdate.Close;
        LQryUpdate.SQL.Text :=
          'UPDATE ge_usuario_empresa_permisos SET ' +
          '  puede_leer = :leer, puede_escribir = :escribir, puede_eliminar = :eliminar, ' +
          '  id_user_update = :user_update, updated_at = CURRENT_TIMESTAMP ' +
          'WHERE id = :id';
        LQryUpdate.ParamByName('leer').AsBoolean := qryPermissions.FieldByName('puede_leer').AsInteger <> 0;
        LQryUpdate.ParamByName('escribir').AsBoolean := qryPermissions.FieldByName('puede_escribir').AsInteger <> 0;
        LQryUpdate.ParamByName('eliminar').AsBoolean := qryPermissions.FieldByName('puede_eliminar').AsInteger <> 0;
        LQryUpdate.ParamByName('user_update').AsString := dmgMain.CurrentUserId;
        LQryUpdate.ParamByName('id').AsString := qryPermissions.FieldByName('id').AsString;
        LQryUpdate.ExecSQL;
        qryPermissions.Next;
      end;
    finally
      qryPermissions.GotoBookmark(LBook);
      qryPermissions.EnableControls;
    end;
  finally
    LQryUpdate.Free;
  end;
end;

procedure TfrmUsuariosEditor.btnSincronizarClick(Sender: TObject);
begin
  if FUserId = '' then
  begin
    ShowMessage('Primero debe guardar el usuario antes de configurar los permisos.');
    Exit;
  end;

  dmgMain.qryExec.Close;
  dmgMain.qryExec.SQL.Text := 
    'INSERT IGNORE INTO ge_usuario_empresa_permisos ' +
    '  (usuario_id, empresa_id, modulo_id, puede_leer, puede_escribir, puede_eliminar, id_user_creator, id_user_update) ' +
    'SELECT ' +
    '  :uid as usuario_id, ' +
    '  e.id as empresa_id, ' +
    '  m.id as modulo_id, ' +
    '  1 as puede_leer, ' +
    '  0 as puede_escribir, ' +
    '  0 as puede_eliminar, ' +
    '  :creator as id_user_creator, ' +
    '  :creator as id_user_update ' +
    'FROM ge_empresas e ' +
    'CROSS JOIN ge_modulos m';
  dmgMain.qryExec.ParamByName('uid').AsString := FUserId;
  dmgMain.qryExec.ParamByName('creator').AsString := dmgMain.CurrentUserId;
  
  try
    dmgMain.qryExec.ExecSQL;
    LoadPermissions;
    ShowMessage('Sincronización completada. Ya puedes configurar los permisos para cada módulo y empresa.');
  except
    on E: Exception do
      ShowMessage('Error al sincronizar permisos: ' + E.Message);
  end;
end;

procedure TfrmUsuariosEditor.btnSaveClick(Sender: TObject);
var
  LIsNew: Boolean;
begin
  if Trim(edtUsername.Text) = '' then
  begin
    ShowMessage('El nombre de usuario es obligatorio.');
    Exit;
  end;

  if Trim(edtNombreCompleto.Text) = '' then
  begin
    ShowMessage('El nombre completo es obligatorio.');
    Exit;
  end;

  if Trim(edtPassword.Text) = '' then
  begin
    ShowMessage('La contraseña es obligatoria.');
    Exit;
  end;

  LIsNew := (FUserId = '');

  dmgMain.qryExec.Close;
  if LIsNew then
  begin
    dmgMain.qryExec.SQL.Text :=
      'INSERT INTO ge_usuarios (username, password_hash, nombre_completo, email, rol, activo, es_admin_global, id_user_creator, id_user_update) ' +
      'VALUES (:username, :password, :nombre, :email, :rol, :activo, :admin, :user, :user)';
  end
  else
  begin
    dmgMain.qryExec.SQL.Text :=
      'UPDATE ge_usuarios SET username = :username, password_hash = :password, nombre_completo = :nombre, ' +
      'email = :email, rol = :rol, activo = :activo, es_admin_global = :admin, id_user_update = :user, updated_at = CURRENT_TIMESTAMP ' +
      'WHERE id = :id';
    dmgMain.qryExec.ParamByName('id').AsString := FUserId;
  end;

  dmgMain.qryExec.ParamByName('username').AsString := Trim(edtUsername.Text);
  dmgMain.qryExec.ParamByName('password').AsString := edtPassword.Text; // Se almacena texto plano o encriptado según flujo del erp
  dmgMain.qryExec.ParamByName('nombre').AsString := Trim(edtNombreCompleto.Text);
  dmgMain.qryExec.ParamByName('email').DataType := ftString;
  if Trim(edtEmail.Text) = '' then
    dmgMain.qryExec.ParamByName('email').Value := Null
  else
    dmgMain.qryExec.ParamByName('email').AsString := Trim(edtEmail.Text);
  dmgMain.qryExec.ParamByName('rol').AsString := cbRol.Text;
  dmgMain.qryExec.ParamByName('activo').AsBoolean := chkActivo.Checked;
  dmgMain.qryExec.ParamByName('admin').AsBoolean := chkAdminGlobal.Checked;
  dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;

  try
    dmgMain.qryExec.ExecSQL;
    // Guardar permisos modificados con auditoría
    SavePermissions;
    ResetChangeTracking;
    if Assigned(FParentForm) and (FParentForm is TfrmUsuarios) then
      TfrmUsuarios(FParentForm).btnRefreshClick(nil);
    ShowMessage('Usuario guardado correctamente.');
  except
    on E: Exception do
      TDbErrorHandler.HandleException(E, dmgMain.qryExec.SQL.Text);
  end;
end;

procedure TfrmUsuariosEditor.btnSalirClick(Sender: TObject);
begin
  ModalResult := mrCancel;
  Close;
end;

procedure TfrmUsuariosEditor.DoAnadir;
begin
  FUserId := '';
  edtUsername.Clear;
  edtNombreCompleto.Clear;
  edtPassword.Clear;
  edtEmail.Clear;
  cbRol.ItemIndex := 3;
  chkActivo.Checked := True;
  chkAdminGlobal.Checked := False;
  edtUsername.SetFocus;
end;

procedure TfrmUsuariosEditor.DoModificar;
begin
  edtUsername.SetFocus;
end;

procedure TfrmUsuariosEditor.DoPrimero;
var
  LParent: TfrmUsuarios;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmUsuarios) then
    LParent := TfrmUsuarios(FParentForm)
  else if Assigned(frmUsuarios) then
    LParent := frmUsuarios;

  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.First;
    FUserId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmUsuariosEditor.DoAnterior;
var
  LParent: TfrmUsuarios;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmUsuarios) then
    LParent := TfrmUsuarios(FParentForm)
  else if Assigned(frmUsuarios) then
    LParent := frmUsuarios;

  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.Prior;
    FUserId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmUsuariosEditor.DoSiguiente;
var
  LParent: TfrmUsuarios;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmUsuarios) then
    LParent := TfrmUsuarios(FParentForm)
  else if Assigned(frmUsuarios) then
    LParent := frmUsuarios;

  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.Next;
    FUserId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmUsuariosEditor.DoUltimo;
var
  LParent: TfrmUsuarios;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmUsuarios) then
    LParent := TfrmUsuarios(FParentForm)
  else if Assigned(frmUsuarios) then
    LParent := frmUsuarios;

  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.Last;
    FUserId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmUsuariosEditor.ToggleAllField(const AFieldName: string; ANewState: Boolean);
var
  LBook: TBookmark;
  LIntVal: Integer;
begin
  if qryPermissions.Active and not qryPermissions.IsEmpty then
  begin
    if ANewState then
      LIntVal := 1
    else
      LIntVal := 0;

    LBook := qryPermissions.GetBookmark;
    qryPermissions.DisableControls;
    try
      qryPermissions.First;
      while not qryPermissions.Eof do
      begin
        qryPermissions.Edit;
        qryPermissions.FieldByName(AFieldName).AsInteger := LIntVal;
        qryPermissions.Post;
        qryPermissions.Next;
      end;
    finally
      qryPermissions.GotoBookmark(LBook);
      qryPermissions.EnableControls;
    end;
  end;
end;

procedure TfrmUsuariosEditor.UpdateTitleCaptions;
  function CheckChar(AState: Boolean): string;
  begin
    if AState then
      Result := #$2611  // ☑
    else
      Result := #$2610; // ☐
  end;
var
  I: Integer;
begin
  for I := 0 to dbgItems.Columns.Count - 1 do
  begin
    if dbgItems.Columns[I].FieldName = 'puede_leer' then
      dbgItems.Columns[I].Title.Caption := CheckChar(FAllLeer) + ' Leer'
    else if dbgItems.Columns[I].FieldName = 'puede_escribir' then
      dbgItems.Columns[I].Title.Caption := CheckChar(FAllEscribir) + ' Escribir'
    else if dbgItems.Columns[I].FieldName = 'puede_eliminar' then
      dbgItems.Columns[I].Title.Caption := CheckChar(FAllEliminar) + ' Eliminar';
  end;
end;

procedure TfrmUsuariosEditor.dbgItemsTitleClick(Column: TColumn);
begin
  if Column.FieldName = 'puede_leer' then
  begin
    FAllLeer := not FAllLeer;
    ToggleAllField('puede_leer', FAllLeer);
  end
  else if Column.FieldName = 'puede_escribir' then
  begin
    FAllEscribir := not FAllEscribir;
    ToggleAllField('puede_escribir', FAllEscribir);
  end
  else if Column.FieldName = 'puede_eliminar' then
  begin
    FAllEliminar := not FAllEliminar;
    ToggleAllField('puede_eliminar', FAllEliminar);
  end
  else
    Exit;
  UpdateTitleCaptions;
end;

procedure TfrmUsuariosEditor.dbgItemsCellClick(Column: TColumn);
var
  LField: TField;
  LNewVal: Integer;
begin
  if (Column <> nil) and (Column.Field <> nil) then
  begin
    LField := Column.Field;
    if (SameText(LField.FieldName, 'puede_leer')) or 
       (SameText(LField.FieldName, 'puede_escribir')) or 
       (SameText(LField.FieldName, 'puede_eliminar')) then
    begin
      if not (qryPermissions.State in [dsEdit, dsInsert]) then
        qryPermissions.Edit;

      if LField.AsInteger <> 0 then
        LNewVal := 0
      else
        LNewVal := 1;

      LField.AsInteger := LNewVal;
      qryPermissions.Post;
    end;
  end;
end;

procedure TfrmUsuariosEditor.dbgItemsKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  LCol: TColumn;
begin
  if (Key = VK_SPACE) and (dbgItems.SelectedIndex >= 0) and (dbgItems.SelectedIndex < dbgItems.Columns.Count) then
  begin
    LCol := dbgItems.Columns[dbgItems.SelectedIndex];
    if (LCol <> nil) and (LCol.Field <> nil) then
    begin
      if (SameText(LCol.Field.FieldName, 'puede_leer')) or 
         (SameText(LCol.Field.FieldName, 'puede_escribir')) or 
         (SameText(LCol.Field.FieldName, 'puede_eliminar')) then
      begin
        dbgItemsCellClick(LCol);
        Key := 0;
      end;
    end;
  end;
end;

end.
